Repository navigation
feat(chat): export a conversation as Markdown, PDF or JSON - #474
Merged
Merged
Conversation
Importing app.domain.ai_tools on its own failed: it imported automations, which loads the actions, and the agent's action subclasses Toolbox before ai_tools had defined it. Running one test file (tests/api/test_chats.py) hit it. ai_tools now imports automations last. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Every answer keeps `meta`: the model and the host name of its server,
the tools offered and asked for, the scope it answered in (after
auto-scope), its limits and what it used, how it ended (answered,
direct, limit, fallback, stopped, error), tool results that read like
instructions to an AI, how long it took, Lens's version, and tokens and
cost from the ledger rows that name it. Questions keep how they were
sent (web, api, voice, room, automation) and their options.
The answer's id is taken once the question is saved and the answer
runs in activity.scope(chat:<cid>, chat_message:<mid>), in the web
chat, chat rooms and the automations' agent; the streamed answer holds
the scope across its steps (activity.scoped). App completions keep no
messages, so their record comes back in lens.record. The stream sends
`meta` before `done`; GET /chats/{cid}/messages/{mid} returns one
message with its record and ledger rows. Each tool step keeps its time
and whether it failed. The web app shows it all from an i in the
answer's header (a sheet on a phone); old answers say they weren't
recorded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…-r960xc-c1-record
The last check asked for 2026-10-17's note after the call without a date had made today's note, so on that day it found the note the deleted root had left behind. It now asks for a day no earlier call makes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
The answer's record uses C2's reserved answer_id for its ledger scope and is saved under the question (parent); a message of any version can be read with its record and ledger rows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
# Conflicts: # threat-model.yaml
GET /api/v1/chats/{cid}/export.{md,json,pdf} gives the version showing,
one message (?message=), or with JSON every version (?all=true) and the
OpenAI {"messages": [...]} shape (?format=messages). Markdown carries
citations as footnotes linking back to Lens and the tools used as a
"Looked up" list; JSON is a tree of OpenAI chat messages with Dublin
Core terms, published as docs/schemas/chat-export.schema.json; the PDF
is that Markdown printed by Chromium, else anytopdf, else LibreOffice,
and 409 when none can run. MCP gains list_chats, read_chat and
export_chat, kept off a namespace's own server.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…record' into claude/project-thread-r960xc-c3-export
The threat model lists a conversation's export as an output: the person's own, with tool summaries and citations, never raw tool results. The OpenAPI schema and client are regenerated with make openapi's two commands. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
The model sometimes cites several passages in one bracket, as the web app already shows; each cited number now becomes its own footnote, and a number with no passage stays as written. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
19 of 22 tasks
C1 (#469), web search (#472) and the sandbox (#473) are in main. The routes keep C5's web pages in citations alongside the export, and the export test now checks a cited web page stays through access changes. The OpenAPI schema and client are regenerated with make openapi's two commands, and the threat model's prose_version is recomputed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Keeps both changelog entries and recomputes the threat model's prose_version; the OpenAPI schema and client regenerate unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
On a loaded runner the merge picker's options were checked while the list before the typing settled was still showing; the lookup for the typed words then emptied it, and the next check found no option. The test now waits for that lookup and checks the options in one step. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…-r960xc-c3-export # Conflicts: # CHANGELOG.md # docs/mcp.md # nextjs-frontend/__tests__/entities-curate.test.tsx # threat-model.yaml
…-r960xc-c3-export Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…-r960xc-c3-export Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
adeelahmad
pushed a commit
that referenced
this pull request
Oct 17, 2026
main now carries the export API as one squashed commit (#474), so this keeps only the web app's copy and export on top of it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
adeelahmad
pushed a commit
that referenced
this pull request
Oct 17, 2026
Brings in conversation export (#474, #484). Exports keep a docs or server citation's source (lens_help) instead of calling it a web page; the export schema lists both. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YHrWfYDCaCbKKDX4w9at9C
This was referenced Oct 17, 2026
adeelahmad
pushed a commit
that referenced
this pull request
Oct 18, 2026
…ay and clipper sign-in as built Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517 and #491 merged. The README's built list now says a conversation exports as Markdown, PDF or JSON and its answers, questions and code copy; a Tools chip picks an answer's tools, limit and namespaces; sandbox runs open under the answer with downloads, and Settings starts the gateway; deep research plans, runs and returns a cited report; ready-made MCP servers (the browser, GitHub) and the optional containers start from the web app; and the web clipper signs in or takes an API token. Each has its checks in docs/requirements.md. In progress drops the merged PRs and lists the open feature PRs; the Planned proof item keeps only the Raspberry Pi timing (#481 and #482 are closed); the docs index links requirements and checks, and published feeds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS
adeelahmad
pushed a commit
that referenced
this pull request
Oct 18, 2026
…ay and clipper sign-in as built Since th 8000 e last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517, #491, #400 and #506 merged. The README's built list now says the assistant answers questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its answers, questions and code copy; a Tools chip picks an answer's tools, limit and namespaces; sandbox runs open under the answer with downloads, and Settings starts the gateway; deep research plans, runs and returns a cited report; ready-made MCP servers, the MCP servers page and the optional containers start from the web app; and the web clipper signs in or takes an API token. Each has its checks in docs/requirements.md. In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing; the docs index links requirements and checks, and published feeds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS
adeelahmad
added a commit
that referenced
this pull request
Oct 18, 2026
…ay and clipper sign-in as built (#536) Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517, #491, #400 and #506 merged. The README's built list now says the assistant answers questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its answers, questions and code copy; a Tools chip picks an answer's tools, limit and namespaces; sandbox runs open under the answer with downloads, and Settings starts the gateway; deep research plans, runs and returns a cited report; ready-made MCP servers, the MCP servers page and the optional containers start from the web app; and the web clipper signs in or takes an API token. Each has its checks in docs/requirements.md. In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing; the docs index links requirements and checks, and published feeds. Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Before: a conversation could only be read in the web app. There was no way to keep it as a file or hand it to another tool, and agents couldn't read it.
After:
GET /api/v1/chats/<id>/export.md,.pdfor.jsondownloads the version that's showing.?message=<mid>downloads one message (an answer comes with its question), and on the JSON export?all=truedownloads every version./resources/<id>?t=<s>), or to the web page. Tools the answer used appear as a "Looked up" list.convert.markdown_page) printed by Chromium if it's installed. Otherwise anytopdf prints it, whether installed, on a conversion node, or fetched on first use unless Settings › Documents says never. Otherwise LibreOffice does. If none of them can run, or printing fails, the endpoint returns 409 with the reason. Nothing heavy was added.mappingandcurrent_node) with Dublin Core terms. An answer's tool calls go intool_messages: an assistanttool_callsmessage, then onetoolmessage per step carrying its summary. Citations, the answer's record, versions, files and the page it was asked from go underlens.?format=messagesgives a single{"messages": [...]}line instead. The schema is atdocs/schemas/chat-export.schema.json, and the tests validate against it.list_chats,read_chatandexport_chat. A newTool.personalflag keeps them off a namespace's own server.Citations follow the reader's current access, the same way
GET /chats/<id>does. No model is called.Motivation and Context
Module C3 of the chat-gaps plan, covering copy and export. This PR is the backend and MCP part. The web app's copy buttons, code-block copy, Export menus and print fallback come in a stacked PR. Built on #469 (C1's answer record), with main merged in.
Types of changes
!in the title)Checklist:
tests/api/test_chat_export.py;test_mcp_namespace.pychecks the personal tools stay off a namespace server).docs/accordingly (docs/assistant.md› Copy and export,docs/mcp.md).## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
list_chats,read_chatandexport_chat.documents.convertersetting.make openapi's two commands.Local checks: targeted pytest
-n 2(65 passed), ruff, ruff format, mypy (151 files), tsc,mkdocs build --strict,ci/validate_threat_model.py, and the OpenAPI diff check.Generated by Claude Code