@oneciel-ai/ciel-runtime 0.2.22 → 0.2.23

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 (86) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +150 -57
  3. package/ciel-runtime-stop.cmd +1 -0
  4. package/ciel-runtime.cmd +1 -0
  5. package/ciel-runtimectl.cmd +1 -0
  6. package/ciel_runtime.py +56 -53
  7. package/ciel_runtime_support/anthropic_tool_turns.py +81 -0
  8. package/ciel_runtime_support/architecture.py +17 -0
  9. package/ciel_runtime_support/channel_injection.py +113 -3
  10. package/ciel_runtime_support/channel_message_prompt.py +15 -0
  11. package/ciel_runtime_support/channel_pending_injection.py +28 -4
  12. package/ciel_runtime_support/channel_pending_poll.py +2 -0
  13. package/ciel_runtime_support/channel_terminal_context.py +6 -0
  14. package/ciel_runtime_support/channel_terminal_dispatch.py +2 -0
  15. package/ciel_runtime_support/channel_terminal_proxy.py +8 -2
  16. package/ciel_runtime_support/channel_wake_claim_repository.py +7 -0
  17. package/ciel_runtime_support/channel_wake_context.py +7 -3
  18. package/ciel_runtime_support/cli_parser.py +6 -0
  19. package/ciel_runtime_support/codex_backend_context.py +4 -0
  20. package/ciel_runtime_support/codex_turn_recovery.py +75 -12
  21. package/ciel_runtime_support/config_migrations.py +102 -10
  22. package/ciel_runtime_support/config_repository.py +9 -0
  23. package/ciel_runtime_support/event_settings_cli.py +284 -2
  24. package/ciel_runtime_support/openai_chat_passthrough.py +2 -0
  25. package/ciel_runtime_support/protocols/openai_responses.py +22 -18
  26. package/ciel_runtime_support/provider_adapters.py +7 -0
  27. package/ciel_runtime_support/provider_policy.py +10 -1
  28. package/ciel_runtime_support/provider_request_builder.py +9 -2
  29. package/ciel_runtime_support/provider_responses_passthrough.py +4 -0
  30. package/ciel_runtime_support/providers/__init__.py +2 -0
  31. package/ciel_runtime_support/providers/alibaba.py +33 -10
  32. package/ciel_runtime_support/providers/constants.py +1 -0
  33. package/ciel_runtime_support/providers/opencode.py +25 -5
  34. package/ciel_runtime_support/providers/opencode_go.py +3 -3
  35. package/ciel_runtime_support/providers/openrouter.py +76 -3
  36. package/ciel_runtime_support/providers/tabitoken.py +130 -0
  37. package/ciel_runtime_support/remote_instructions.py +20 -6
  38. package/ciel_runtime_support/remote_memory.py +515 -55
  39. package/ciel_runtime_support/responses_input_compatibility.py +14 -0
  40. package/ciel_runtime_support/router_http.py +5 -0
  41. package/ciel_runtime_support/runtime_constants.py +1 -1
  42. package/ciel_runtime_support/runtime_launch.py +9 -2
  43. package/ciel_runtime_support/runtime_paths.py +2 -0
  44. package/ciel_runtime_support/tool_side_effect_dedupe.py +7 -4
  45. package/ciel_runtime_support/transcript_delta_delivery.py +333 -0
  46. package/ciel_runtime_support/usage_events.py +45 -3
  47. package/ciel_runtime_support/usage_service.py +871 -0
  48. package/ciel_runtime_support/windows_conpty.py +108 -2
  49. package/ciel_runtime_support/workspace_mcp.py +4 -0
  50. package/docs/CLI-Reference.md +11 -0
  51. package/docs/Configuration.md +7 -2
  52. package/docs/MCP-Channels.md +7 -3
  53. package/docs/Module-Map.md +4 -0
  54. package/docs/Providers.md +32 -0
  55. package/docs/Remote-Memory.md +29 -10
  56. package/docs/Test-Suite.md +1 -0
  57. package/docs/journal/2026/08/20/kevin-wake-orphan-tool-repair.md +161 -0
  58. package/docs/journal/2026/08/20/nightly-push-local-deployment.md +59 -2
  59. package/docs/journal/2026/08/20/wing-remote-memory-replication-verification.md +107 -0
  60. package/docs/journal/2026/08/21/alibaba-singapore-qwen38-official-alignment.md +64 -0
  61. package/docs/journal/2026/08/21/codex-visible-sse-wake-submit-regression.md +85 -0
  62. package/docs/journal/2026/08/21/external-sse-wake-only-change-analysis.md +131 -0
  63. package/docs/journal/2026/08/21/hyundai-invalid-replayed-tool-name-repair.okf +88 -0
  64. package/docs/journal/2026/08/21/local-stale-temporary-runtime-pin-repair.okf +118 -0
  65. package/docs/journal/2026/08/21/transcript-compaction-external-events.okf +131 -0
  66. package/docs/journal/2026/08/21/wing-ai-net-mcp-boundary-diagnosis.okf +129 -0
  67. package/docs/journal/2026/08/21/wing-cold-start-wake-submit-repair.okf +161 -0
  68. package/docs/journal/2026/08/21/wing-powershell-memory-runaway.okf +101 -0
  69. package/docs/journal/2026/08/21/wing-repeated-reasoning-tool-turn-repair.okf +112 -0
  70. package/docs/journal/2026/08/21/wing-repeated-tool-guard-finalization.okf +81 -0
  71. package/docs/journal/2026/08/21/wing-working-directory-memory-deployment.md +110 -0
  72. package/docs/journal/2026/08/21/wing-workspace-state-memory-prompt-injection.md +88 -0
  73. package/docs/journal/2026/08/22/alibaba-singapore-dual-endpoint-wire-alignment.okf +142 -0
  74. package/docs/journal/2026/08/22/delluhiold-claude-compaction-empty-tool-name.okf +168 -0
  75. package/docs/journal/2026/08/22/remote-memory-relative-prompt-paths.okf +79 -0
  76. package/docs/journal/2026/08/22/remote-memory-root-system-prompt.okf +121 -0
  77. package/docs/journal/2026/08/23/claude-web-chat-stateless-mcp-repair.okf +97 -0
  78. package/docs/journal/2026/08/23/cross-runtime-token-usage-interfaces-research.okf +378 -0
  79. package/docs/journal/2026/08/23/main-0.2.23-readme-release.okf +81 -0
  80. package/docs/journal/2026/08/23/mia-openrouter-ox-alpha-latency-diagnosis.okf +146 -0
  81. package/docs/journal/2026/08/23/openrouter-opencode-ox-alpha-support.okf +114 -0
  82. package/docs/journal/2026/08/23/tabitoken-provider-model-catalog.okf +97 -0
  83. package/docs/journal/2026/08/23/usage-stream-snapshot-implementation.okf +134 -0
  84. package/docs/usage-observability.md +141 -0
  85. package/install.ps1 +24 -3
  86. package/package.json +2 -1
@@ -7,6 +7,7 @@ import codecs
7
7
  import subprocess
8
8
  import sys
9
9
  import threading
10
+ import time
10
11
  from collections.abc import Mapping
11
12
  from typing import Any, Callable
12
13
 
@@ -36,6 +37,10 @@ class WindowsConPtySession:
36
37
  """Own a Windows pseudo console and expose it as a byte input transport."""
37
38
 
38
39
  separate_input_stages = False
40
+ supports_prompt_ready_wait = True
41
+ # Codex collapses long pastes to a placeholder, so the captured output tail
42
+ # cannot prove that the original prompt prefix is present. The snapshot is
43
+ # still usable for comparing output before and after a submit key.
39
44
  supports_input_snapshot = False
40
45
 
41
46
  def __init__(
@@ -61,6 +66,7 @@ class WindowsConPtySession:
61
66
  self._write_lock = threading.Lock()
62
67
  self._output_lock = threading.Lock()
63
68
  self._output_tail = bytearray()
69
+ self._output_total_bytes = 0
64
70
  self._closed = False
65
71
  self._stop = threading.Event()
66
72
  self._stdin_console_handle: Any = None
@@ -87,9 +93,108 @@ class WindowsConPtySession:
87
93
  def normalize_prompt(prompt: str) -> str:
88
94
  return " ".join(str(prompt or "").replace("\t", " ").splitlines()).strip()
89
95
 
96
+ def input_snapshot(self) -> str:
97
+ """Return the captured child-output tail for submission confirmation."""
98
+
99
+ return self.output_tail().decode("utf-8", errors="replace")
100
+
101
+ def prompt_readiness_checkpoint(self) -> int:
102
+ """Return an absolute child-output position for prompt render checks."""
103
+
104
+ with self._output_lock:
105
+ return int(self._output_total_bytes)
106
+
107
+ def wait_until_prompt_ready(
108
+ self,
109
+ previous_snapshot: object,
110
+ timeout_seconds: float = 2.0,
111
+ *,
112
+ expected_prompt: str | None = None,
113
+ ) -> bool:
114
+ """Wait for the child to render and settle after injected prompt input."""
115
+
116
+ if isinstance(previous_snapshot, int) and not isinstance(previous_snapshot, bool):
117
+ return self._wait_for_prompt_output(
118
+ previous_snapshot,
119
+ timeout_seconds,
120
+ expected_prompt,
121
+ )
122
+
123
+ baseline = str(previous_snapshot or "")
124
+ last = baseline
125
+ rendered = False
126
+ stable_since: float | None = None
127
+ deadline = time.monotonic() + max(0.0, float(timeout_seconds))
128
+ while True:
129
+ now = time.monotonic()
130
+ current = self.input_snapshot()
131
+ if self._prompt_rendered_since(baseline, current, expected_prompt):
132
+ rendered = True
133
+ if rendered and current != last:
134
+ last = current
135
+ stable_since = now
136
+ elif rendered and stable_since is not None and now - stable_since >= 0.5:
137
+ return True
138
+ if now >= deadline:
139
+ return False
140
+ time.sleep(0.02)
141
+
142
+ def _wait_for_prompt_output(
143
+ self,
144
+ checkpoint: int,
145
+ timeout_seconds: float,
146
+ expected_prompt: str | None,
147
+ ) -> bool:
148
+ """Observe bytes emitted after input instead of comparing a rolling tail."""
149
+
150
+ cursor = max(0, int(checkpoint))
151
+ observed = bytearray()
152
+ deadline = time.monotonic() + max(0.0, float(timeout_seconds))
153
+ while True:
154
+ chunk, cursor = self._output_since(cursor)
155
+ if chunk:
156
+ observed.extend(chunk)
157
+ del observed[: max(0, len(observed) - 64 * 1024)]
158
+ if self._prompt_rendered_in_output(bytes(observed), expected_prompt):
159
+ return True
160
+ if time.monotonic() >= deadline:
161
+ return False
162
+ time.sleep(0.02)
163
+
164
+ def _output_since(self, checkpoint: int) -> tuple[bytes, int]:
165
+ with self._output_lock:
166
+ total = int(self._output_total_bytes)
167
+ base = total - len(self._output_tail)
168
+ start = max(base, min(total, int(checkpoint)))
169
+ return bytes(self._output_tail[start - base :]), total
170
+
90
171
  @staticmethod
91
- def input_snapshot() -> None:
92
- return None
172
+ def _prompt_rendered_in_output(
173
+ output: bytes,
174
+ expected_prompt: str | None,
175
+ ) -> bool:
176
+ prompt = WindowsConPtySession.normalize_prompt(str(expected_prompt or ""))
177
+ if not prompt:
178
+ return bool(output)
179
+ prefix = prompt[:48].encode("utf-8", errors="replace")
180
+ return bool(prefix and prefix in output) or b"[Pasted Content" in output
181
+
182
+ @staticmethod
183
+ def _prompt_rendered_since(
184
+ baseline: str,
185
+ current: str,
186
+ expected_prompt: str | None,
187
+ ) -> bool:
188
+ if current == baseline:
189
+ return False
190
+ prompt = WindowsConPtySession.normalize_prompt(str(expected_prompt or ""))
191
+ prefix = prompt[:48]
192
+ if prefix and current.count(prefix) > baseline.count(prefix):
193
+ return True
194
+ paste_marker = "[Pasted Content"
195
+ if prompt and current.count(paste_marker) > baseline.count(paste_marker):
196
+ return True
197
+ return not prompt
93
198
 
94
199
  @staticmethod
95
200
  def pending_input_events() -> None:
@@ -560,6 +665,7 @@ class WindowsConPtySession:
560
665
  break
561
666
  with self._output_lock:
562
667
  self._output_tail.extend(data)
668
+ self._output_total_bytes += len(data)
563
669
  del self._output_tail[: max(0, len(self._output_tail) - 64 * 1024)]
564
670
  if self._mirror_output:
565
671
  self._mirror_bytes(data)
@@ -246,11 +246,15 @@ class WorkspaceMcpLaunchService:
246
246
  runtime: str,
247
247
  config: dict[str, Any],
248
248
  environment: Mapping[str, str] | None = None,
249
+ injected_servers: Mapping[str, Any] | None = None,
249
250
  ) -> WorkspaceMcpLaunch:
250
251
  if runtime not in _RUNTIMES:
251
252
  raise ValueError(f"Unsupported workspace MCP runtime: {runtime}")
252
253
  self.recover_stale()
253
254
  servers = workspace_mcp_servers(config, runtime)
255
+ if injected_servers:
256
+ injected = {"workspace_mcp": {"servers": dict(injected_servers)}}
257
+ servers.update(workspace_mcp_servers(injected, runtime))
254
258
  if not servers:
255
259
  return WorkspaceMcpLaunch()
256
260
  launch_id = uuid.uuid4().hex
@@ -115,6 +115,17 @@ ciel-runtimectl api-keys [PROVIDER] [KEY1] [KEY2] ...
115
115
 
116
116
  ### Router 관리
117
117
 
118
+ #### `transcript-events`
119
+
120
+ ```bash
121
+ ciel-runtimectl transcript-events enabled=true url=https://memory.example/v1/transcripts authorization='Bearer {MEMORY_TOKEN}'
122
+ ```
123
+
124
+ 현재 런타임의 Claude/Codex JSONL 트랜스크립트를 등록한 HTTP(S) 주소로 증분
125
+ 전송한다. URL과 트랜스크립트별 바이트 커서는 워크스페이스 상태에 저장되며 HTTP
126
+ 2xx 이후에만 전진한다. `start_mode=tail`은 현재 실행 경계 이후만,
127
+ `start_mode=beginning`은 파일 처음부터 보낸다. 설정 확인은 인자 없이 실행한다.
128
+
118
129
  #### `remote-memory`
119
130
 
120
131
  ```bash
@@ -273,8 +273,13 @@ ciel-runtimectl remote-memory enabled=true \
273
273
 
274
274
  manifest는 인덱스 상대경로, 폴더 구조를 나타내는 파일 상대경로, 각 파일의 다운로드
275
275
  URL과 포맷을 제공한다. 지원 포맷은 OKF, Markdown, JSON, YAML, TOML, 일반 텍스트다.
276
- 동기화 후 실제 메모리 내용 대신 `Memory index: .ciel/memory/<index>` 줄만
277
- `CLAUDE.md`, `AGENTS.md`, `GEMINI.md` 하단의 관리 블록에 추가한다.
276
+ 동기화 후 실제 메모리 내용 전체를 프롬프트에 넣지 않는다. 대신 내부적으로 검증된 메모리
277
+ 루트와 인덱스의 워크스페이스 기준 상대경로, 그리고 인덱스부터 읽고 관련 메모리 파일을 참조하라는 지시를 최종
278
+ wire 시스템/개발자 문맥 하단의 관리 블록에 추가한다. 같은 블록을 원격으로 내려받은
279
+ `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`의 마지막에도 기록하여 라우터를 우회하는
280
+ 네이티브 CLI 실행에서도 동일하게 참조할 수 있게 한다. Remote Instructions의 성공
281
+ 상태와 해당 다운로드 파일이 확인된 경우에만 네이티브 파일을 갱신하며, Remote Memory
282
+ 단독 설정으로 지시 파일을 새로 만들거나 확인되지 않은 사용자 파일을 변경하지 않는다.
278
283
 
279
284
  계약, 제한, 예시는 [Remote Memory](Remote-Memory.md)를 참고한다.
280
285
 
@@ -61,9 +61,13 @@ how a request enters the active TUI and how the answer should leave it:
61
61
  active CLI terminal. `input_transport=router` keeps the full message in the
62
62
  Runtime Input Gateway: while a model turn is already active, its next request
63
63
  consumes the message without a console wake; while the CLI is idle, Ciel types
64
- only a short wake marker and replaces that marker with the full pending message
65
- in the next request body. Router transport therefore requires the active model
66
- session to use Ciel Router. Aliases `llm`, `context`, and `inband` are accepted.
64
+ a correlated wake turn and replaces that entire turn with the full pending
65
+ message in the next request body. Codex shows the wake marker followed by the
66
+ original projected message in its TUI; Claude and other CLI adapters retain the
67
+ short marker-only wake because their terminal turn behavior is different. In
68
+ both cases the model request receives the queued message exactly once. Router
69
+ transport therefore requires the active model session to use Ciel Router.
70
+ Aliases `llm`, `context`, and `inband` are accepted.
67
71
  - `response_mode=web_chat` (alias `ai_net`, default) uses the normal correlated Web
68
72
  Chat reply contract. `response_mode=tty` leaves the model's ordinary terminal
69
73
  output as-is and does not require a Web Chat tool reply. `response_mode=mcp`
@@ -577,6 +577,10 @@ Ollama model-context cache 일치, dynamic `num_ctx` bucket, preset cap, option/
577
577
 
578
578
  OpenRouter 인증, OpenAI protocol capability와 hosted context 정책을 소유하는 독립 Adapter.
579
579
 
580
+ ### `ciel_runtime_support/providers/tabitoken.py`
581
+
582
+ TaBiAI의 OpenAI Chat/Anthropic Messages 이중 endpoint, Bearer 인증, 공개 모델 fallback과 thinking 모델의 `reasoning_effort` 투영을 소유하는 독립 Adapter.
583
+
580
584
  ### `ciel_runtime_support/providers/lm_studio.py`
581
585
 
582
586
  LM Studio catalog 경로, local capability, loaded-model readiness와 context/status 정책을 소유하는 독립 Adapter.
package/docs/Providers.md CHANGED
@@ -21,6 +21,7 @@
21
21
  | `nvidia-hosted` | Nvidia Hosted | OpenAI Chat | NVIDIA NIM Cloud |
22
22
  | `self-hosted-nim` | Self Hosted NIM | OpenAI Chat | 로컬 NIM |
23
23
  | `openrouter` | OpenRouter | OpenAI Chat | `https://openrouter.ai/api` |
24
+ | `tabitoken` | TaBiAI (Tabitoken.com) | Anthropic Messages / OpenAI Chat | `https://tabitoken.com` |
24
25
  | `fireworks` | Fireworks.ai | OpenAI Chat | `https://api.fireworks.ai/inference` |
25
26
  | `xai` | xAI | OpenAI Responses / Chat | `https://api.x.ai/v1` |
26
27
 
@@ -44,6 +45,25 @@ token은 Bearer header로 전송하며, 사용자 지정 `base_url`로 사설 ga
44
45
  `base_url`로 명시해야 한다. “수동 OAuth token” 범주는 transport가 구현된 상태를
45
46
  뜻하며, Ciel이 브라우저 로그인이나 refresh token을 대신 발급한다는 뜻은 아니다.
46
47
 
48
+ ### Alibaba Model Studio Singapore
49
+
50
+ `alims-intl`의 기본 모델은 Singapore International scope의 `qwen3.8-max`다.
51
+ 공식 한도에 맞춰 context window는 1,000,000, 최대 입력은 일반 모드 991,808,
52
+ thinking 모드 983,616, 최대 출력은 131,072로 취급한다. Codex에는 thinking 모드의
53
+ 안전한 입력 상한인 983,616을 model catalog context로 제공한다.
54
+
55
+ 신규 workspace endpoint는 계정별 Workspace ID가 필요하므로 Ciel이 이를 추측하지
56
+ 않는다. Model Studio console에서 발급된 다음 OpenAI-compatible URL을 `base_url`에
57
+ 입력한다.
58
+
59
+ ```text
60
+ https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1
61
+ ```
62
+
63
+ 이 URL을 사용하면 Claude용 native endpoint는 같은 workspace의
64
+ `/apps/anthropic`으로 파생된다. `alitoken`은 별도 구독·과금 계약의 Singapore Token
65
+ Plan endpoint를 계속 사용하므로 일반 `alims-intl` workspace URL과 혼용하지 않는다.
66
+
47
67
  ### Anthropic Messages 호환 제공자
48
68
 
49
69
  `minimax`와 `minimax-cn`은 OpenAI 변환을 거치지 않고 Anthropic Messages 계약,
@@ -198,6 +218,18 @@ Anthropic thinking 객체나 문서로 확인되지 않은 GLM-5.2 effort 문자
198
218
  - 단일 API로 다수 모델 접근.
199
219
  - OpenAI Chat 호환.
200
220
 
221
+ ## TaBiAI (Tabitoken.com)
222
+
223
+ - 내부 ID: `tabitoken` (`tabi`, `tabiai`, `tabi-token` 별칭 지원).
224
+ - 공식 공개 pricing catalog의 활성 모델 4개를 기본 catalog로 제공한다:
225
+ `claude-opus-4-8`, `claude-opus-4-8-thinking`, `claude-opus-5`,
226
+ `claude-opus-5-thinking`.
227
+ - Claude 요청은 `POST /v1/messages`, Codex/OpenAI 요청은
228
+ `POST /v1/chat/completions`로 보낸다.
229
+ - 모든 endpoint에 `Authorization: Bearer <TOKEN>`을 사용한다. 공식 API Detail에
230
+ 표시된 `x-api-key`는 Anthropic 형식 endpoint가 추가로 허용하는 대체 인증이다.
231
+ - `-thinking` 모델의 OpenAI Chat 요청에만 `reasoning_effort`를 전달한다.
232
+
201
233
  ---
202
234
 
203
235
  ## Fireworks.ai
@@ -2,8 +2,10 @@
2
2
 
3
3
  Remote Memory downloads a workspace-scoped memory tree from a separate HTTP
4
4
  manifest endpoint before an interactive runtime starts. It does not place the
5
- memory contents in the system prompt. Ciel adds only the local memory-index
6
- address to the bottom of the runtime's native instruction file.
5
+ memory contents in the system prompt. Ciel appends the verified local memory
6
+ root, memory-index address, and an instruction to consult the relevant memory
7
+ files to the downloaded native instruction file and keeps the same managed
8
+ block at the end of routed system/developer prompt context.
7
9
 
8
10
  ## Configure and synchronize
9
11
 
@@ -15,11 +17,13 @@ ciel-runtimectl remote-memory `
15
17
  sync
16
18
  ```
17
19
 
18
- The default destination is `.ciel/memory` below the launch workspace. A custom
19
- destination must remain a portable relative path inside the workspace:
20
+ The default destination is `.ciel/memory` below the runtime's actual launch
21
+ workspace. On Windows, launching from `C:\work\project` produces
22
+ `C:\work\project\.ciel\memory`. A custom destination must remain a portable
23
+ relative path inside that launch workspace:
20
24
 
21
25
  ```powershell
22
- ciel-runtimectl remote-memory directory=.ciel/team-memory
26
+ ciel-runtimectl remote-memory directory=team-memory
23
27
  ```
24
28
 
25
29
  Use `ciel-runtimectl remote-memory` without values to display the current
@@ -78,18 +82,33 @@ and optional SHA-256 values pass does Ciel replace the previous memory tree.
78
82
  Files omitted by the new manifest therefore disappear. A failed synchronization
79
83
  keeps the previous tree unchanged and logs `remote_memory_failed`.
80
84
 
81
- After a successful synchronization, Ciel appends this managed block at the
82
- bottom of `CLAUDE.md`, `AGENTS.md`, or `GEMINI.md` as appropriate:
85
+ The router's launch-workspace identity selects the destination. A machine with
86
+ no separate project checkout may use its user home as the launch workspace; in
87
+ that case Ciel creates `<home>/.ciel/memory`. Different launch workspaces receive
88
+ different trees. Ciel keeps only synchronization metadata in its workspace
89
+ state directory.
90
+
91
+ After a successful synchronization, routed OpenAI Responses, OpenAI Chat, and
92
+ Anthropic Messages requests receive a managed prompt block like this:
83
93
 
84
94
  ```markdown
85
95
  <!-- ciel-runtime:remote-memory:begin -->
96
+ Memory root: .ciel/memory
86
97
  Memory index: .ciel/memory/index.okf
98
+ Memory guidance: Resolve these paths from the current workspace root. Read the memory index first and use the relevant files under the memory root as context for your work.
87
99
  <!-- ciel-runtime:remote-memory:end -->
88
100
  ```
89
101
 
90
- The block is replaced instead of duplicated on later launches. Remote
91
- Instruction refreshes and context compaction also restore the pointer after
92
- rewriting the native instruction file.
102
+ The resolved absolute address is verified to remain below the current launch
103
+ workspace, but the projected prompt uses a portable workspace-relative path so
104
+ the workspace can be moved or replicated. When the matching Remote Instructions download has a verified
105
+ state and native file, Ciel appends the block idempotently to that downloaded
106
+ `CLAUDE.md`, `AGENTS.md`, or `GEMINI.md` and restores it after an instruction
107
+ refresh. Remote Memory by itself does not create a native instruction file or
108
+ modify an unverified user-owned file. Routed requests remove any earlier copy
109
+ and place one verified copy at the final system/developer prompt tail. Disabling
110
+ Remote Memory removes only the Ciel-managed block while preserving the rest of
111
+ the instruction file.
93
112
 
94
113
  ## Safety and limits
95
114
 
@@ -32,6 +32,7 @@
32
32
  | `test_ollama_provider_options.py` | Ollama 옵션 처리 |
33
33
  | `test_opencode_provider.py` | OpenCode 제공자 |
34
34
  | `test_openrouter_provider.py` | OpenRouter 제공자 |
35
+ | `test_tabitoken_provider.py` | TaBiAI (Tabitoken.com) 제공자 |
35
36
  | `test_vllm_provider.py` | vLLM 제공자 |
36
37
  | `test_zai_provider.py` | Z.AI 제공자 |
37
38
 
@@ -77,3 +77,164 @@
77
77
  Kevin-session restart followed by delivery of pending ID 3478 is still
78
78
  required for live end-to-end confirmation. The task is not recorded as live
79
79
  complete until that observation is obtained.
80
+
81
+ ## 2026-08-20 queued-wake recurrence
82
+
83
+ - A Kevin-session screenshot shows Codex `Queued follow-up inputs` containing
84
+ message IDs 3636 through 3639.
85
+ - IDs 3637, 3638, and 3639 appear more than once; ID 3638 appears repeatedly
86
+ in consecutive queue entries. This is not a queue containing each pending
87
+ message exactly once.
88
+ - Queueing a new follow-up while a Codex turn is active is expected client
89
+ behavior. Repeated copies of the same `[ciel-wake] pending_ids=<id>` are not
90
+ evidence of distinct messages and are not treated as normal delivery.
91
+ - Source inspection rules out `submit_retries=4` alone as the explanation on
92
+ Windows ConPTY: confirmation retries require a tmux pane snapshot; without a
93
+ snapshot the injector stops after its first submit attempt.
94
+ - The screenshot does not identify whether duplicate entries originated from
95
+ repeated Ciel poll/claim injection or repeated input generation on Kevin's
96
+ host. Kevin's router log for IDs 3636-3639 is required to distinguish those
97
+ paths. No code change was made from this screenshot alone.
98
+
99
+ ## 2026-08-20 live queued-wake root cause
100
+
101
+ ### Kevin host evidence
102
+
103
+ - The Kevin host was reached as `kevin-codex@100.95.132.58`; hostname
104
+ `aap-pool-hera`.
105
+ - The affected process used Ciel Runtime
106
+ `0.2.21-nightly.20260821-003258.0ea4cce` and Codex CLI `0.149.0` on Linux.
107
+ - The persisted Codex session is
108
+ `01a00b77-e165-75a3-8255-17f55de2ebcf`.
109
+ - Its transcript records a turn from `2026-08-21T02:31:13.240Z` through
110
+ `2026-08-21T02:48:00.098Z`.
111
+ - Runtime input records 3636 through 3639 arrived while that turn was running
112
+ (the runtime-input file stores host-local times `21:40:34` through
113
+ `21:44:03`).
114
+ - The TUI screenshot shows those IDs in `Queued follow-up inputs`, including
115
+ repeated copies. Neither the Codex transcript nor `~/.codex/history.jsonl`
116
+ contains an executed/submitted record for IDs 3636 through 3639.
117
+ - The turn completed and the TUI then displayed
118
+ `No tool output found for custom tool call call_KjPlhUWIbFkQDJD6ywRc8ZCJ`;
119
+ the queued follow-ups remained stranded until the session was restarted.
120
+ - After restart, IDs 3648 and 3649 were submitted once and entered a new turn,
121
+ confirming that AI-Net receipt and Ciel runtime-input persistence were not
122
+ the blocked stage.
123
+
124
+ ### Confirmed code defect
125
+
126
+ - The Windows terminal poller blocks injection when either a tool call or the
127
+ whole model turn is active.
128
+ - The POSIX terminal poller used only `active_tool_call`. It therefore injected
129
+ wake text during model execution between tool calls. Codex placed that text
130
+ in its internal follow-up queue instead of starting a turn.
131
+ - OpenAI Codex source defines queued follow-ups as input held until the current
132
+ turn finishes and drains them through `maybe_send_next_queued_input()`.
133
+ OpenAI issue 37974 documents a Codex failure path in which the TUI is ready
134
+ but queued follow-ups remain permanently stranded. The Kevin transcript does
135
+ not contain a prompt-edit failure record, so that particular Codex trigger is
136
+ not attributed to Kevin; only the observed stranded-queue state is used.
137
+
138
+ ### Repair
139
+
140
+ - `ChannelTerminalPolling` now receives both `active_tool_call` and
141
+ `active_turn` signals and exposes `input_busy()` as their logical OR.
142
+ - The POSIX pending-message poller uses `input_busy()`, matching the existing
143
+ Windows safety boundary.
144
+ - This is transport-wide behavior for supported CLI runtimes; it contains no
145
+ Kevin, agent-ID, model, or provider exception.
146
+
147
+ ### Verification
148
+
149
+ - Focused channel/transcript/architecture tests: 485 passed, 122 skipped.
150
+ - Full repository tests:
151
+ - unit: 1,105 passed, 44 skipped
152
+ - router: 902 passed
153
+ - channel: 370 passed, 80 skipped
154
+ - runtime: 244 passed, 12 skipped
155
+ - total: 2,621 passed, 136 skipped
156
+
157
+ ## 2026-08-20 live validation correction
158
+
159
+ ### Contradicting live result
160
+
161
+ - Commit `81fd842` and nightly
162
+ `0.2.21-nightly.20260821-031340.81fd842` were installed on Kevin.
163
+ - During an active turn, a local-only verification notification was admitted
164
+ as runtime input 3667. The input file advanced to 3667 while the delivery
165
+ cursor remained 3666.
166
+ - Contrary to the expected deferral, the Codex TUI displayed input 3667 under
167
+ `Messages to be submitted after next tool call`. The first repair was
168
+ therefore insufficient and was not treated as complete.
169
+ - After the turn ended, the verification input did run and produced the exact
170
+ requested marker once as an assistant response, but the active-turn
171
+ injection itself remained a confirmed defect.
172
+
173
+ ### Confirmed second defect
174
+
175
+ - Kevin's resumed Codex transcript is 276,346,204 bytes.
176
+ - For the tested turn, `task_started` was at byte offset 275,665,220 and the
177
+ first verification-marker record was at offset 276,344,102: a distance of
178
+ 678,882 bytes.
179
+ - `ChannelTranscriptRepository.read_tail_text` reads at most 524,288 bytes.
180
+ Thus the active-turn parser could no longer see the opening lifecycle event
181
+ once one turn emitted more than the diagnostic-tail limit, and it returned
182
+ inactive before the turn completed.
183
+
184
+ ### Second repair
185
+
186
+ - Transcript scope initialization now records the exact end offset of an
187
+ existing resumed-session transcript before the CLI process starts.
188
+ - Active-turn detection incrementally reads complete JSONL records appended
189
+ after that boundary and persists the last lifecycle state between polls.
190
+ - The state closes only when a later lifecycle/assistant record closes the
191
+ turn. Output volume no longer determines whether the opening event remains
192
+ visible.
193
+ - The implementation is runtime/session scoped and contains no Kevin, agent,
194
+ provider, or model exception.
195
+
196
+ ### Second-repair verification
197
+
198
+ - A regression test appends `task_started`, more than 600 KiB of reasoning,
199
+ and `task_complete` to a resumed transcript. The incremental reader retains
200
+ the start state beyond the old 512 KiB boundary and later observes the
201
+ completion.
202
+ - Focused transcript/channel/architecture tests: 497 passed, 122 skipped.
203
+ - Full repository tests:
204
+ - unit: 1,105 passed, 44 skipped
205
+ - router: 902 passed
206
+ - channel: 372 passed, 80 skipped
207
+ - runtime: 244 passed, 12 skipped
208
+ - total: 2,623 passed, 136 skipped
209
+ - Ruff and diff checks passed.
210
+
211
+ ### Second-repair deployment and live result
212
+
213
+ - Commit `a6b8c58` was pushed to `nightly`.
214
+ - GitHub CI run `32443885914` succeeded.
215
+ - npm publish/tarball verification run `32443885966` succeeded.
216
+ - Published and installed Kevin version:
217
+ `0.2.21-nightly.20260821-033715.a6b8c58`; registry `gitHead` is
218
+ `a6b8c58c6a83f3208158bcac27f853797cf61df7`.
219
+ - After restart, runtime input 3674 was admitted during the active turn that
220
+ ran from `2026-08-21T03:43:32.213Z` to `03:43:56.729Z`.
221
+ - Six seconds after admission, the input file contained 3674, the delivery
222
+ cursor still read 3673, and the TUI contained zero matches for the marker,
223
+ `pending_ids=3674`, or `Messages to be submitted`.
224
+ - After the active turn completed, a new turn ran from `03:43:57.378Z` to
225
+ `03:44:01.320Z`. The assistant emitted the exact verification marker once,
226
+ the delivery cursor advanced to 3674, and the visible TUI contained zero
227
+ internal queue labels.
228
+
229
+ ### Kevin MCP credential repair observed during deployment
230
+
231
+ - The restarted process contained `AINET_API_KEY`, and the same credential
232
+ returned HTTP 200 from `/api/v1/agents/me`; environment projection was not
233
+ missing.
234
+ - Kevin's pre-existing `ai-net-http` Codex entry contained a different static
235
+ bearer token, while the managed `ai-net` entry matched the current
236
+ credential. That static mismatch produced the observed 401.
237
+ - `ai-net-http` was replaced through `codex mcp` with
238
+ `bearer_token_env_var = "AINET_API_KEY"` and no static authorization header.
239
+ - After the final restart, the captured TUI contained zero `ai-net-http`
240
+ startup failures and zero `invalid agent API key` messages.
@@ -36,5 +36,62 @@
36
36
  modules and task journals.
37
37
  - Branch synchronization before commit: local `nightly` and `origin/nightly`
38
38
  had zero commits of divergence.
39
- - Published version, workflow results, local installation, and runtime evidence
40
- are recorded after the registry artifact becomes available.
39
+ - Commit: `0ea4cce` (`fix: harden runtime replay and MCP environment`).
40
+ - Published npm version:
41
+ `0.2.21-nightly.20260821-003258.0ea4cce`.
42
+ - Registry `gitHead`: `0ea4ccefce5955e3dbd37eff3b38cf8e122e0a75`.
43
+ - Publish workflow `32432946530`: success, including npm test, publish, and
44
+ published-tarball verification.
45
+ - CI workflow `32432946541`: success for minimum Python, unit, router,
46
+ channel, runtime, quality, documentation, and package checks.
47
+ - The exact registry version was installed globally on the local workstation.
48
+ Its package manifest reports the nightly version above.
49
+ - Source and installed SHA-256 values match for all three changed runtime
50
+ modules:
51
+ - `responses_input_compatibility.py`:
52
+ `A0B38961262AC7870317F99C892C4A1535525979C93EF632A177571C3C8DCDC2`
53
+ - `runtime_launch.py`:
54
+ `036E0323E6DD9FBF5B5DD3EC591017CB47FDCB2EE7CB6361C47143F734BDD609`
55
+ - `workspace_mcp.py`:
56
+ `C79DE505E5CA27DF0562DC006038CB68DAB1B2D1FA010D4E9160B1B38384AAAF`
57
+ - Installed-module runtime checks:
58
+ - the reported orphan custom call ID was removed while its following user
59
+ message remained;
60
+ - generic MCP projection produced `Authorization=Bearer test-token`,
61
+ `X-Env=projected`, and retained the static header;
62
+ - `ciel-runtime --version` exited successfully and printed the base runtime
63
+ compatibility version `0.2.21`; the nightly artifact identity is verified
64
+ separately from the installed package manifest and npm registry metadata.
65
+
66
+ ## Remote memory and routed-turn recovery release
67
+
68
+ - Commits pushed to `nightly`:
69
+ - `c93e4b6` — remote workspace-memory manifest synchronization
70
+ - `fb1f871` — exact missing tool-output verdict repair
71
+ - `6d7ffb8` — bounded routed-Codex empty-end-turn recovery
72
+ - Published npm version:
73
+ `0.2.21-nightly.20260821-040635.6d7ffb8`.
74
+ - Registry `gitHead`:
75
+ `6d7ffb8a90a37a662d1721ae33476f5a545c130d`.
76
+ - CI workflow `32445593344`: success.
77
+ - Publish workflow `32445593325`: success, including npm test, publish, and
78
+ published-tarball verification.
79
+ - The published tarball contains `remote_memory.py`, `Remote-Memory.md`,
80
+ `codex_turn_recovery.py`, and `responses_input_compatibility.py`.
81
+ - The exact published version was installed globally on Wing through SSH port
82
+ 1004. Installed and source SHA-256 values match:
83
+ - `remote_memory.py`:
84
+ `C00F72CD469436D597D8D9C286C714E65F6B890A06324AC63C811AB0192750A0`
85
+ - `codex_turn_recovery.py`:
86
+ `270D064CDBCEEB4E4D84351F8481620A5B9B219B80740DB81F4E9EBD22DE7F37`
87
+ - `responses_input_compatibility.py`:
88
+ `B69FB5690F0BC2AD0253F79689D6A7195A5AD95117874E8CEF3ACFE4B4289279`
89
+ - Wing's installed `ciel-runtimectl remote-memory` command exited 0 and
90
+ reported all manifest, destination, authorization, timeout, and size-limit
91
+ settings.
92
+ - An installed-module probe on Wing presented the exact runtime-generated
93
+ empty-end-turn notice. It performed exactly one retry, accepted the visible
94
+ retry result, and did not replay the synthetic notice into the retry request.
95
+ - No Python, Node, or Codex process remained active on Wing after installation,
96
+ so no stale in-memory runtime needs to be terminated; the next launch loads
97
+ the installed version.