agent-interlude 0.1.0__tar.gz
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.
- agent_interlude-0.1.0/.gitignore +7 -0
- agent_interlude-0.1.0/LICENSE +21 -0
- agent_interlude-0.1.0/PKG-INFO +397 -0
- agent_interlude-0.1.0/README.md +371 -0
- agent_interlude-0.1.0/README.zh-TW.md +318 -0
- agent_interlude-0.1.0/docs/release.md +108 -0
- agent_interlude-0.1.0/dogfood.sh +151 -0
- agent_interlude-0.1.0/pyproject.toml +96 -0
- agent_interlude-0.1.0/src/agent_interlude/__init__.py +12 -0
- agent_interlude-0.1.0/src/agent_interlude/__main__.py +12 -0
- agent_interlude-0.1.0/src/agent_interlude/analyze.py +292 -0
- agent_interlude-0.1.0/src/agent_interlude/proxy.py +547 -0
- agent_interlude-0.1.0/src/agent_interlude/report.py +3823 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2026 Zonda Yang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-interlude
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Intercept and log AI coding agent <-> API traffic for prompt-architecture analysis.
|
|
5
|
+
Project-URL: Homepage, https://github.com/zondatw/agent-interlude
|
|
6
|
+
Project-URL: Repository, https://github.com/zondatw/agent-interlude
|
|
7
|
+
Project-URL: Issues, https://github.com/zondatw/agent-interlude/issues
|
|
8
|
+
Author: Zonda Yang
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,claude-code,codex,llm-observability,prompt-analysis,reverse-proxy,sse
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
23
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# agent-interlude
|
|
28
|
+
|
|
29
|
+
English · **[繁體中文](README.zh-TW.md)**
|
|
30
|
+
|
|
31
|
+
agent-interlude intercepts the traffic between an AI coding agent (**Claude Code**,
|
|
32
|
+
**Codex**) and its API, persisting the prompt structure (`system` / `tools` /
|
|
33
|
+
`messages`) of every request/response pair as JSONL. Use it to analyze the
|
|
34
|
+
fixed skeleton vs. dynamic slots of a prompt, and to compare across agents.
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
Both agents let you override the API base URL via an environment variable, so
|
|
39
|
+
no transparent MITM or certificate forgery is needed. agent-interlude is an
|
|
40
|
+
**explicit reverse proxy**:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Claude Code ──(A) plain HTTP──▶ agent-interlude proxy ──(B) normal HTTPS──▶ api.anthropic.com
|
|
44
|
+
localhost:8788 (proxy re-encrypts as the client)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Segment (A) has no TLS, so the proxy reads the plaintext body straight off the
|
|
48
|
+
socket — that's the interception point, and it never touches credentials.
|
|
49
|
+
Responses are copied as they stream through a relay, then the SSE events are
|
|
50
|
+
reassembled and archived once the stream ends. The agent notices nothing.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
Install once with [`pipx`](https://pipx.pypa.io/) (recommended) or
|
|
55
|
+
[`uv tool`](https://docs.astral.sh/uv/concepts/tools/) — both put the
|
|
56
|
+
`agent-interlude` command on your PATH in an isolated environment, no
|
|
57
|
+
project-level setup needed:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pipx install agent-interlude
|
|
61
|
+
# or
|
|
62
|
+
uv tool install agent-interlude
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Requires Python 3.11+ and the agent CLIs you want to capture
|
|
66
|
+
(`claude` and/or `codex`). Zero runtime dependencies — agent-interlude is
|
|
67
|
+
stdlib-only.
|
|
68
|
+
|
|
69
|
+
For contributors hacking on agent-interlude itself, see
|
|
70
|
+
[Development setup](#development-setup) below.
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 1. One command: starts the 3 proxy listeners AND the web UI on :8000
|
|
76
|
+
agent-interlude
|
|
77
|
+
|
|
78
|
+
# 2. In another terminal, point Claude Code at it
|
|
79
|
+
ANTHROPIC_BASE_URL=http://localhost:8788 claude
|
|
80
|
+
|
|
81
|
+
# 3. Open the live browser UI as captures stream in
|
|
82
|
+
open http://127.0.0.1:8000/timeline
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
On startup the bundled launcher prints:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
[agent-interlude] claude: http://127.0.0.1:8788 -> https://api.anthropic.com
|
|
89
|
+
[agent-interlude] codex: http://127.0.0.1:8789 -> https://api.openai.com (Codex + API key)
|
|
90
|
+
[agent-interlude] codex: http://127.0.0.1:8790 -> https://chatgpt.com (Codex + ChatGPT login)
|
|
91
|
+
[agent-interlude] logging to .agent-interlude/log-<timestamp>.jsonl
|
|
92
|
+
[agent-interlude] web UI: http://127.0.0.1:8000/timeline (auto-started; disable with --no-ui)
|
|
93
|
+
[agent-interlude-report] http://127.0.0.1:8000
|
|
94
|
+
[agent-interlude-report] watching .agent-interlude/log-*.jsonl
|
|
95
|
+
[agent-interlude-report] auto-reload on (disable with --no-reload)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`.agent-interlude/log-<timestamp>.jsonl` lands under your current working
|
|
99
|
+
directory (not next to the installed module), so run `agent-interlude` from
|
|
100
|
+
wherever you want the logs collected.
|
|
101
|
+
|
|
102
|
+
The web UI runs in a child process. `Ctrl-C` on `agent-interlude` tears down
|
|
103
|
+
both proxy and UI cleanly.
|
|
104
|
+
|
|
105
|
+
Each launch opens a fresh log file; every request prints one line such as
|
|
106
|
+
`[claude] POST /v1/messages`.
|
|
107
|
+
|
|
108
|
+
Variants:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
agent-interlude --no-ui # proxy-only (e.g. CI / headless capture)
|
|
112
|
+
agent-interlude --ui-port 9000 # bind the UI on a different port
|
|
113
|
+
agent-interlude-report serve # UI only, against existing logs
|
|
114
|
+
agent-interlude-analyze # text report, no server
|
|
115
|
+
python -m agent_interlude # module-form, equivalent to `agent-interlude`
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development setup
|
|
119
|
+
|
|
120
|
+
To hack on agent-interlude itself, clone and use the source layout directly:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git clone https://github.com/zondatw/agent-interlude.git
|
|
124
|
+
cd agent-interlude
|
|
125
|
+
uv sync # installs the package in editable mode
|
|
126
|
+
uv run agent-interlude # runs from src/agent_interlude/
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
For contributors: install [`pre-commit`](https://pre-commit.com/) and
|
|
130
|
+
[`gitleaks`](https://github.com/gitleaks/gitleaks) (`brew install pre-commit gitleaks`),
|
|
131
|
+
then run `pre-commit install` once. Subsequent `git commit` will auto-run
|
|
132
|
+
ruff lint+format, hygiene checks (trailing whitespace, EOF, private keys,
|
|
133
|
+
yaml/toml syntax), codespell, and gitleaks. Run `pre-commit run --all-files`
|
|
134
|
+
to check the whole tree at once.
|
|
135
|
+
|
|
136
|
+
Release flow (PyPI Trusted Publishing via the `beta` and `release`
|
|
137
|
+
branches) is documented in [`docs/release.md`](docs/release.md).
|
|
138
|
+
|
|
139
|
+
## Pointing an agent at the proxy
|
|
140
|
+
|
|
141
|
+
### Claude Code
|
|
142
|
+
|
|
143
|
+
An environment variable is enough (Claude Code appends `/v1/messages` to the
|
|
144
|
+
base URL itself):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
ANTHROPIC_BASE_URL=http://localhost:8788 claude
|
|
148
|
+
# Non-interactive:
|
|
149
|
+
ANTHROPIC_BASE_URL=http://localhost:8788 claude -p "say hi"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Codex
|
|
153
|
+
|
|
154
|
+
Codex's built-in `openai` provider **does not honor** a base-URL override
|
|
155
|
+
(`OPENAI_BASE_URL` is ignored), so you must define a custom provider. Pick the
|
|
156
|
+
route that matches your login method.
|
|
157
|
+
|
|
158
|
+
#### A. ChatGPT login (recommended, no API key)
|
|
159
|
+
|
|
160
|
+
Point the custom provider at the proxy's **chatgpt.com** listener (port 8790,
|
|
161
|
+
path `/backend-api/codex`). Codex sends your ChatGPT token, the proxy forwards
|
|
162
|
+
to the real `https://chatgpt.com/backend-api/codex/responses`, and the response
|
|
163
|
+
is recorded too:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
codex exec -s read-only \
|
|
167
|
+
-c model_provider=agent-interlude \
|
|
168
|
+
-c 'model_providers.agent-interlude.base_url="http://localhost:8790/backend-api/codex"' \
|
|
169
|
+
-c 'model_providers.agent-interlude.wire_api="responses"' \
|
|
170
|
+
"say hi"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
For a durable setup, write it into `~/.codex/config.toml`:
|
|
174
|
+
|
|
175
|
+
```toml
|
|
176
|
+
[model_providers.agent-interlude]
|
|
177
|
+
name = "agent-interlude"
|
|
178
|
+
base_url = "http://localhost:8790/backend-api/codex"
|
|
179
|
+
wire_api = "responses"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Then switch to it per-invocation with `-c model_provider=agent-interlude` (do **not**
|
|
183
|
+
set a top-level `model_provider`, or Codex breaks whenever the proxy is down):
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
codex -c model_provider=agent-interlude exec -s read-only "say hi"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### B. OpenAI API key
|
|
190
|
+
|
|
191
|
+
If you have an `OPENAI_API_KEY` with the `api.responses.write` scope, point at
|
|
192
|
+
the proxy's **api.openai.com** listener instead (port 8789, path `/v1`):
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
codex exec -s read-only \
|
|
196
|
+
-c model_provider=agent-interlude \
|
|
197
|
+
-c 'model_providers.agent-interlude.base_url="http://localhost:8789/v1"' \
|
|
198
|
+
-c 'model_providers.agent-interlude.wire_api="responses"' \
|
|
199
|
+
"say hi"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
> **Note** — Using ChatGPT login but pointing at `api.openai.com` (route B
|
|
203
|
+
> without a key) returns **401** (the ChatGPT token lacks the
|
|
204
|
+
> `api.responses.write` scope). The request is still recorded in full; you just
|
|
205
|
+
> get no response back — switch to route A instead.
|
|
206
|
+
|
|
207
|
+
## What gets recorded
|
|
208
|
+
|
|
209
|
+
Logs live in `.agent-interlude/log-<timestamp>.jsonl`. Each exchange is **two lines**
|
|
210
|
+
paired by `id`:
|
|
211
|
+
|
|
212
|
+
```jsonc
|
|
213
|
+
// kind="request"
|
|
214
|
+
{"id":"ab12…","kind":"request","agent":"claude","wire":"claude-messages",
|
|
215
|
+
"headers_kept":{…}, // authorization / x-api-key already filtered out
|
|
216
|
+
"request":{…full parsed body…},
|
|
217
|
+
"extract":{"system":…,"tools":…,"messages":…}}
|
|
218
|
+
|
|
219
|
+
// kind="response" (same id)
|
|
220
|
+
{"id":"ab12…","kind":"response","agent":"claude","status":200,
|
|
221
|
+
"stream":true,"event_count":7,"event_types":{…},
|
|
222
|
+
"reconstructed":{"model":"…","text":"…","usage":{…},"tool_uses":[…]}}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A non-streaming response (e.g. Codex's 401) is recorded as
|
|
226
|
+
`"stream":false,"body":{…}` instead.
|
|
227
|
+
|
|
228
|
+
Supported wire formats: `claude-messages` (`/v1/messages`), `codex-responses`
|
|
229
|
+
(`/responses`), `codex-chat` (`/chat/completions`).
|
|
230
|
+
|
|
231
|
+
## Analysis
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
agent-interlude-analyze # read every log in .agent-interlude
|
|
235
|
+
agent-interlude-analyze --agent claude # one agent only
|
|
236
|
+
agent-interlude-analyze --max-slots 30 # print more dynamic slots
|
|
237
|
+
agent-interlude-analyze path/to/log.jsonl # a specific file / glob
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The report covers:
|
|
241
|
+
|
|
242
|
+
- Each agent's system size and **fixed skeleton vs. dynamic slots** (e.g. the
|
|
243
|
+
`git status` and date that Claude injects are flagged as dynamic slots).
|
|
244
|
+
- The tools list, count, and schema key (Claude=`input_schema` /
|
|
245
|
+
Codex=`parameters`).
|
|
246
|
+
- A cross-agent structure comparison table.
|
|
247
|
+
|
|
248
|
+
> To surface Codex's dynamic slots, run a few sessions with **different prompts /
|
|
249
|
+
> at different times** (multiple retries of the same prompt share one system
|
|
250
|
+
> prompt and count as just 1 distinct sample).
|
|
251
|
+
|
|
252
|
+
## Web UI
|
|
253
|
+
|
|
254
|
+
For a browsable view of the same data — with per-request drill-in,
|
|
255
|
+
skeleton-vs-slot highlighting in context, and a tools schema browser —
|
|
256
|
+
launch the local web UI:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
agent-interlude-report serve # http://127.0.0.1:8000 (default)
|
|
260
|
+
agent-interlude-report serve --port 9000
|
|
261
|
+
agent-interlude-report serve --logs "other/path/log-*.jsonl"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Routes:
|
|
265
|
+
|
|
266
|
+
| Path | What it shows |
|
|
267
|
+
|---|---|
|
|
268
|
+
| `/` | Cross-agent overview + per-agent stats |
|
|
269
|
+
| `/timeline[?agent=…&since=…&from=…&to=…&session_gap=…]` | Sequence-diagram view of every exchange: agent ↔ API lanes, two arrows per exchange (request + response), auto-grouped into sessions (gap-threshold configurable), per-hour density histogram on top, RTT bars on every response arrow. Click an arrow to expand only that half (request → system/tools/messages; response → reassembled text/usage/event_types). |
|
|
270
|
+
| `/requests[?agent=…]` | Sortable list of exchanges with model / token columns |
|
|
271
|
+
| `/requests/<id>` | Collapsible system / tools / messages + paired reassembled response |
|
|
272
|
+
| `/skeleton/<agent>` | Canonical system sample with fixed lines greyed and dynamic slots highlighted in yellow |
|
|
273
|
+
| `/tools/<agent>` | Collapsible JSON schema per tool |
|
|
274
|
+
|
|
275
|
+
Every HTML page has a matching `/api/<same path>` endpoint that returns the
|
|
276
|
+
same data as JSON — built in from day one so future features (token usage
|
|
277
|
+
charts, search/filter, live update) consume a stable backend instead of
|
|
278
|
+
re-scraping HTML. The page nav surfaces the JSON URL on every view.
|
|
279
|
+
|
|
280
|
+
Bound to `127.0.0.1` only (the logs hold full prompts; never expose them on
|
|
281
|
+
LAN). The JSONL loader is mtime-cached, so re-reads stay cheap while the
|
|
282
|
+
proxy keeps appending — just refresh the page to see new captures.
|
|
283
|
+
|
|
284
|
+
## One-command end-to-end verification
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
./dogfood.sh
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Starts the proxy → fires one Claude and one Codex call → verifies both request
|
|
291
|
+
and response were recorded with zero credential leakage → tears the proxy down,
|
|
292
|
+
and finally prints `RESULT: PASS`.
|
|
293
|
+
|
|
294
|
+
## Manual verification (step by step)
|
|
295
|
+
|
|
296
|
+
To confirm each link in the chain by hand (rather than just running
|
|
297
|
+
`dogfood.sh`):
|
|
298
|
+
|
|
299
|
+
**Terminal 1** — start the proxy:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
agent-interlude
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Terminal 2** — send one message through Claude Code; it should reply `PONG`
|
|
306
|
+
(proving the relay + streaming are intact):
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
ANTHROPIC_BASE_URL=http://localhost:8788 claude -p "Reply with exactly the word PONG and nothing else."
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Back in Terminal 1, the proxy console should show a line:
|
|
313
|
+
`[claude] POST /v1/messages`.
|
|
314
|
+
|
|
315
|
+
**Check the log landed** (a structural summary that does not dump prompt
|
|
316
|
+
contents):
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
LOG=$(ls -t .agent-interlude/log-*.jsonl | head -1)
|
|
320
|
+
uv run python - "$LOG" <<'PY'
|
|
321
|
+
import json, re, sys
|
|
322
|
+
recs = [json.loads(l) for l in open(sys.argv[1], encoding="utf-8")]
|
|
323
|
+
for r in recs:
|
|
324
|
+
if r.get("kind", "request") == "request":
|
|
325
|
+
ex = r.get("extract") or {}
|
|
326
|
+
present = [k for k in ("system", "tools", "messages") if ex.get(k) is not None]
|
|
327
|
+
print(f"REQ {r['agent']:<7} {r['wire']:<16} extract={present}")
|
|
328
|
+
else:
|
|
329
|
+
txt = (r.get("reconstructed") or {}).get("text")
|
|
330
|
+
info = f"text={txt!r}" if r.get("stream") else f"body={type(r.get('body')).__name__}"
|
|
331
|
+
print(f"RESP {r['agent']:<7} status={r['status']:<3} {info[:70]}")
|
|
332
|
+
blob = "\n".join(json.dumps(r) for r in recs)
|
|
333
|
+
leaks = re.findall(r"Bearer\s+\S{20,}|sk-ant-\S{20,}|eyJ[\w-]{10,}\.eyJ[\w-]{10,}", blob)
|
|
334
|
+
print("\ncredential leaks:", len(leaks))
|
|
335
|
+
PY
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
You should see at least one pair:
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
REQ claude claude-messages extract=['system', 'tools', 'messages']
|
|
342
|
+
RESP claude status=200 text='PONG'
|
|
343
|
+
|
|
344
|
+
credential leaks: 0
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
(The first line may be `REQ claude unknown` → `RESP claude status=404`; that's
|
|
348
|
+
Claude Code's connection pre-check `HEAD /` and can be ignored.)
|
|
349
|
+
|
|
350
|
+
**View the structure analysis**:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
agent-interlude-analyze
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
When done, press `Ctrl-C` in Terminal 1 to shut the proxy down.
|
|
357
|
+
|
|
358
|
+
## Security notes
|
|
359
|
+
|
|
360
|
+
- `.agent-interlude/` contains the **full prompt** (your code, possibly secrets) → it
|
|
361
|
+
is gitignored; **do not commit or share it.**
|
|
362
|
+
- Auth headers (`authorization` / `x-api-key` / `cookie`) are forwarded only and
|
|
363
|
+
**never written to the log**; `headers_kept` retains an allowlist of fields
|
|
364
|
+
only.
|
|
365
|
+
- The proxy strips the request's `accept-encoding`, so the recorded bytes are
|
|
366
|
+
always plaintext (no gzip/br to deal with).
|
|
367
|
+
- To check for rogue connections: `lsof -nP -iTCP -sTCP:ESTABLISHED | grep
|
|
368
|
+
Python`, and confirm the proxy only connects to `api.anthropic.com` /
|
|
369
|
+
`api.openai.com` / `chatgpt.com`.
|
|
370
|
+
|
|
371
|
+
## Adding a new agent
|
|
372
|
+
|
|
373
|
+
Edit the `LISTENERS` list at the top of `src/agent_interlude/proxy.py` and add a
|
|
374
|
+
row `(port, upstream_host, label)`. Wire detection lives in `detect_wire()`,
|
|
375
|
+
and field normalization in `extract()` (requests) and `reconstruct()`
|
|
376
|
+
(responses).
|
|
377
|
+
|
|
378
|
+
## Troubleshooting
|
|
379
|
+
|
|
380
|
+
| Symptom | Cause / fix |
|
|
381
|
+
|---|---|
|
|
382
|
+
| `port 8788 already in use` | A previous proxy is still running. `lsof -nP -iTCP:8788 -sTCP:LISTEN` to find the PID → `kill <PID>` |
|
|
383
|
+
| Codex isn't recorded, startup prints `provider: openai` | You used the `OPENAI_BASE_URL` shortcut, which the built-in provider ignores. Use a custom provider instead |
|
|
384
|
+
| Codex returns 401 (missing `api.responses.write` scope) | You're on ChatGPT login but pointing at `api.openai.com` (8789). Switch to route A (8790 + `/backend-api/codex`); see "Pointing an agent at the proxy › Codex" |
|
|
385
|
+
| Agent refuses `http://` | Fall back to TLS: the proxy terminates TLS with a self-signed CA, and Claude Code trusts it via `NODE_EXTRA_CA_CERTS` (not needed currently) |
|
|
386
|
+
|
|
387
|
+
## Files
|
|
388
|
+
|
|
389
|
+
| Path | Purpose |
|
|
390
|
+
|---|---|
|
|
391
|
+
| `src/agent_interlude/proxy.py` | Three-listener reverse proxy, streaming relay + SSE tee/reassembly. Entry point: `agent-interlude` |
|
|
392
|
+
| `src/agent_interlude/analyze.py` | Cross-request diff, fixed skeleton vs. dynamic slots, cross-agent comparison (text report). Entry point: `agent-interlude-analyze` |
|
|
393
|
+
| `src/agent_interlude/report.py` | Local web UI (HTML + JSON) over the same analysis. Entry point: `agent-interlude-report` |
|
|
394
|
+
| `dogfood.sh` | One-command end-to-end verification (contributor-facing, not shipped in the wheel) |
|
|
395
|
+
| `docs/release.md` | PyPI Trusted Publishing setup + per-release flow |
|
|
396
|
+
| `.github/workflows/` | `beta.yml` (push to `beta` → test.pypi.org), `release.yml` (push to `release` → pypi.org) |
|
|
397
|
+
| `.agent-interlude/` | JSONL output, written under the user's cwd (gitignored, sensitive) |
|