$npx -y skills add axoviq-ai/synthadoc --skill sessionExtract conversation turns from AI session history files (.jsonl)
| 1 | # Session Skill |
| 2 | |
| 3 | Extracts human-readable conversation turns from AI coding session history files |
| 4 | (`.jsonl`). Supports two formats: |
| 5 | |
| 6 | - **Claude Code** — the JSONL format written by Anthropic's Claude Code CLI |
| 7 | (`~/.claude/projects/<hash>/<session-id>.jsonl`) |
| 8 | - **Codex / Cursor** — the simpler `{"role": ..., "content": ...}` per-line format |
| 9 | used by OpenAI Codex and Cursor IDE sessions |
| 10 | |
| 11 | Format is detected automatically from the first parseable line. |
| 12 | |
| 13 | ## What gets extracted |
| 14 | |
| 15 | Only substantive conversation turns are kept: |
| 16 | |
| 17 | | Content type | Action | |
| 18 | |---|---| |
| 19 | | User text messages | Kept if ≥ 3 words | |
| 20 | | Assistant text responses | Kept if ≥ 20 words | |
| 21 | | Assistant thinking blocks | Skipped (internal reasoning, not final output) | |
| 22 | | Tool use / tool result blocks | Skipped (avoids leaking file contents or credentials) | |
| 23 | | Image / attachment blocks | Skipped | |
| 24 | | Sub-agent scaffolding (`isSidechain: true`) | Skipped (internal sub-agent turns) | |
| 25 | | Session metadata lines | Skipped (`permission-mode`, `file-history-snapshot`, `system`, `last-prompt`) | |
| 26 | |
| 27 | The extracted text is then passed through Synthadoc's standard pre-LLM source sanitizer |
| 28 | (zero-width characters, bidi overrides, HTML comments, hidden CSS spans, base64 blobs, |
| 29 | instruction-override phrases), exactly like PDF, DOCX, URL, and every other source type. |
| 30 | |
| 31 | ## Output format |
| 32 | |
| 33 | Each turn is labelled `[USER]` or `[ASSISTANT]` and separated by `---`: |
| 34 | |
| 35 | ``` |
| 36 | [USER] |
| 37 | How do I implement a sliding window algorithm? |
| 38 | |
| 39 | --- |
| 40 | |
| 41 | [ASSISTANT] |
| 42 | A sliding window algorithm maintains a contiguous subarray (the "window") … |
| 43 | ``` |
| 44 | |
| 45 | ## `suggested_slug` |
| 46 | |
| 47 | The skill returns a `suggested_slug` in metadata derived from the session file's |
| 48 | modification time and the first substantive user message: |
| 49 | |
| 50 | ``` |
| 51 | session-2026-07-15-how-do-i-implement-a-sliding |
| 52 | ``` |
| 53 | |
| 54 | ## Limitations |
| 55 | |
| 56 | - **No chunking in v1.1** — very long sessions are truncated at the |
| 57 | `max_source_chars` limit (default 400 000 chars). Multi-session archives |
| 58 | should be split into individual `.jsonl` files before ingesting. |
| 59 | - **Tool output excluded** — tool result blocks (shell output, file reads, etc.) |
| 60 | are stripped. This is intentional: it avoids leaking file contents and |
| 61 | credentials into the wiki. |
| 62 | - **Format auto-detection** — detection inspects the first 30 parseable lines. |
| 63 | Corrupt or empty files produce an empty `ExtractedContent`. |
| 64 | - **No deduplication across ingest runs** — re-ingesting the same session file |
| 65 | creates or updates the same wiki page (standard ingest dedup applies via |
| 66 | source hash). |
| 67 | |
| 68 | ## When this skill is used |
| 69 | |
| 70 | - Source path ends with `.jsonl` |
| 71 | - Intent phrases: `"claude session"`, `"codex session"`, `"cursor session"`, |
| 72 | `"ai session"`, `"session history"` |
| 73 | |
| 74 | ## Standalone usage |
| 75 | |
| 76 | ```python |
| 77 | import asyncio |
| 78 | from synthadoc.skills.session.scripts.main import SessionSkill |
| 79 | |
| 80 | skill = SessionSkill() |
| 81 | |
| 82 | async def main(): |
| 83 | result = await skill.extract("/path/to/session.jsonl") |
| 84 | print(result.text) # [USER]\n...\n\n---\n\n[ASSISTANT]\n... |
| 85 | print(result.metadata) # {"format": "claude_code", "turn_count": 42, "suggested_slug": "..."} |
| 86 | |
| 87 | asyncio.run(main()) |
| 88 | ``` |