# sess


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

``` python
from fastcore.test import *
from importlib.resources import files
from aidialog.dialog import Dialog
from aidialog.dlgskill import summary_dlg
from aidialog.ipynb import read_ipynb
import shutil, tempfile
```

Claude Code sessions and Codex threads are stored differently and read
by different modules, but a person reaching for one has only an id and a
question.
[`find_sess`](https://AnswerDotAI.github.io/llmsurgery/sess.html#find_sess)
takes that id, from either host, and says which host owns it and where
the transcript is;
[`sess_dlg`](https://AnswerDotAI.github.io/llmsurgery/sess.html#sess_dlg)
reads it into a dialog for the aidialog tools. Ids may be given as any
unique prefix.

Where `ant.sess2dlg` and `oai.thread2dlg` are faithful conversions,
[`sess_dlg`](https://AnswerDotAI.github.io/llmsurgery/sess.html#sess_dlg)
is a reading view: it drops the host’s bookkeeping and the harness’s
injected turns, and it reaches back through compactions by default, so
the dialog holds the conversation rather than the machinery around it.
The
[`sess2nb`](https://AnswerDotAI.github.io/llmsurgery/sess.html#sess2nb)
command line writes that view to an ipynb.

## Finding a session

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/llmsurgery/blob/main/llmsurgery/sess.py#L30"
target="_blank" style="float:right; font-size:smaller">source</a>

### find_sess

``` python
def find_sess(
    ref:NoneType=None, # Session id or unique id prefix; the current session if None
    cwd:NoneType=None, # Project directory, for Claude sessions
    codex_home:NoneType=None, # Codex home; `oai.CODEX_HOME` if None
):
```

*The host owning session `ref` and its transcript path:
`('ant'|'oai', path)`*

The fixtures the `ant` and `oai` notebooks use are ordinary transcripts,
so putting one where each host keeps its sessions is enough to find it.
A Claude transcript is named by its session id, and eight characters of
that id are plenty:

``` python
proj = Path(tempfile.mkdtemp())
sd = ant.sess_dir(proj)
sd.mkdir(parents=True)
shutil.copy(Path(files('llmsurgery')/'data'/'ant'/'source.jsonl'), sd/'ab12cd34-0000-4000-8000-000000000000.jsonl')
find_sess('ab12cd34', proj)
```

A Codex rollout is found the same way, from a thread id embedded in a
longer filename, so
[`find_sess`](https://AnswerDotAI.github.io/llmsurgery/sess.html#find_sess)
also reports which host owns the id it was given:

``` python
home = Path(tempfile.mkdtemp())
ses = home/'sessions'/'2026'/'01'
ses.mkdir(parents=True)
shutil.copy(Path(files('llmsurgery')/'data'/'oai'/'source.jsonl'), ses/'rollout-2026-01-24T02-44-30-ef56ab78.jsonl')
find_sess('ef56ab78', codex_home=home)
```

## Reading a session

Both hosts record a transcript in append order, so reading the whole
history needs no chain walking: for Claude, `ant.conv_recs` keeps the
conversation records in the order they happened, and for Codex,
`oai.response_items` returns every recorded item, superseded history
included. `since_compact` swaps each for its narrower counterpart,
`ant.sess_thread` and `oai.active_items`, giving only what the model
would see now.

Filtering is what makes this a reading view rather than a transcript.
Compaction summaries go, because with the full history present they
restate what is already there. Turns the harness injected go too: a
skill body arrives as a user turn indistinguishable from a typed one
except by its opening line. What remains is what a person said and what
the assistant said back.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/llmsurgery/blob/main/llmsurgery/sess.py#L64"
target="_blank" style="float:right; font-size:smaller">source</a>

### sess_chat

``` python
def sess_chat(
    host, # `'ant'` for a Claude session, `'oai'` for a Codex thread
    path, # The transcript path, e.g. from `find_sess`
    since_compact:bool=False, # Only the conversation since the last compaction?
):
```

*Canonical messages for the conversation recorded in `path`, oldest
first*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/llmsurgery/blob/main/llmsurgery/sess.py#L101"
target="_blank" style="float:right; font-size:smaller">source</a>

### sess_dlg

``` python
def sess_dlg(
    ref:NoneType=None, # Session id or unique id prefix; the current session if None
    cwd:NoneType=None, # Project directory, for Claude sessions
    codex_home:NoneType=None, # Codex home; `oai.CODEX_HOME` if None
    name:NoneType=None, # Dialog name; the transcript's id if None
    mx:int=10, # Maximum characters per rendered tool input/output string; None disables truncation
    since_compact:bool=False, # Only the conversation since the last compaction?
    strip_tools:bool=False, # Drop tool calls and their results entirely?
):
```

*The conversation of a Claude session or Codex thread as a dialog, ready
to read or save*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/llmsurgery/blob/main/llmsurgery/sess.py#L84"
target="_blank" style="float:right; font-size:smaller">source</a>

### path_dlg

``` python
def path_dlg(
    host, # `'ant'` for a Claude session, `'oai'` for a Codex thread
    path, # The transcript path, e.g. from `find_sess`
    name:NoneType=None, # Dialog name; the transcript's stem if None
    mx:int=10, # Maximum characters per rendered tool input/output string; None disables truncation
    since_compact:bool=False, # Only the conversation since the last compaction?
    strip_tools:bool=False, # Drop tool calls and their results entirely?
):
```

*The conversation of the transcript at `path` as a dialog, its nb meta
recording source and time span*

``` python
sess_dlg('ab12cd34', proj).summary()
```

The dialog is self-describing: nb-level meta records which transcript it
came from and the conversation’s true time span (first and last record
timestamps), and each message’s cell meta carries its source record’s
`created` time and `uid`. File times can lie – opening an old session
bumps its mtime – so provenance lives in the data:

``` python
md = sess_dlg('ab12cd34', proj)
lm = md.meta['llmsurgery']
test_eq(lm['host'], 'ant')
assert lm['source'].endswith('.jsonl')
assert lm['created'] <= lm['last']
assert all(m.meta['created'] and m.meta['uid'] for m in md.messages)
lm
```

Claude transcripts can contain the same record twice (chain restarts
replay records into the append-order file). A replayed record is the
same message, so the reading view keeps the first occurrence –
duplicating every line of a transcript changes nothing:

``` python
ap = sd/'ab12cd34-0000-4000-8000-000000000000.jsonl'
lines = ap.read_text().splitlines()
ap.write_text('\n'.join(lines+lines)+'\n')
d2 = sess_dlg('ab12cd34', proj)
test_eq([m.id for m in d2.messages], [m.id for m in md.messages])
test_eq([m.content for m in d2.messages], [m.content for m in md.messages])
```

`strip_tools` leaves only what was said, which is the form to read when
the question is what was decided rather than what was run:

``` python
bare = sess_dlg('ab12cd34', proj, strip_tools=True)
assert not any('{.tool}' in (m.ai_res or '') for m in bare.messages)
bare.summary()
```

## The command line

[`sess2nb`](https://AnswerDotAI.github.io/llmsurgery/sess.html#sess2nb)
writes that dialog to an ipynb, so a session becomes a notebook to read,
search, or paste from. It returns the path when called from Python and
prints it when run as a command.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/llmsurgery/blob/main/llmsurgery/sess.py#L116"
target="_blank" style="float:right; font-size:smaller">source</a>

### sess2nb

``` python
def sess2nb(
    ref:str, # Session id or unique id prefix
    Out:str=None, # Output path; `<id>.ipynb` in the current directory if None
    mx:int=10, # Maximum characters per rendered tool input/output string
    since_compact:bool=False, # Only the conversation since the last compaction?
    strip_tools:bool=False, # Drop tool calls and their results entirely?
):
```

*Write a Claude session or Codex thread to an ipynb dialog*

``` python
with tempfile.TemporaryDirectory() as td:
    out = sess2nb('ab12cd34', f'{td}/sess.ipynb')
    saved = read_ipynb(out)
test_eq(len(saved.messages), len(sess_dlg('ab12cd34', proj).messages))
out.name
```

## Cleanup

The Claude fixture has to sit in `~/.claude/projects` to be findable, so
remove it again along with both scratch homes.

``` python
shutil.rmtree(sd)
shutil.rmtree(proj)
shutil.rmtree(home)
```
