Core#

The building blocks of a conversation: messages, the code blocks inside them that tools execute, and the log that stores, loads, and branches a conversation. See Usage for managing conversations, and Glossary for terminology.

Message#

A message in the conversation.

class gptme.message.Message#

A message in the assistant conversation.

role#

The role of the message sender (system, user, or assistant).

content#

The content of the message.

timestamp#

The timestamp of the message.

files#

Files attached to the message, could e.g. be images for vision.

pinned#

Whether this message should be pinned to the top of the chat, and never context-trimmed.

hide#

Whether this message should be hidden from the chat output (but still be sent to the assistant).

quiet#

Whether this message should be printed on execution (will still print on resume, unlike hide). This is not persisted to the log file.

metadata#

Optional metadata including token usage and cost information.

__init__(role: ~typing.Literal['system', 'user', 'assistant'], content: str, timestamp: ~datetime.datetime = <factory>, files: list[~pathlib.Path | ~gptme.util.uri.URI] = <factory>, file_hashes: dict[str, str] = <factory>, call_id: str | None = None, pinned: bool = False, hide: bool = False, quiet: bool = False, ephemeral_ttl: int | None = None, metadata: ~gptme.message.MessageMetadata | None = None, terminal_display_content: str | None = None) None#
concat(other: Message, separator: str = '\n\n') Message#

Concatenate two messages of the same role.

Merges content with separator, and combines files and file_hashes. Preserves timestamp from the first message.

Parameters:
  • other – Message to concatenate with this one

  • separator – String to join content with (default: “nn”)

Returns:

New Message with merged content and files

Raises:

ValueError – If messages have different roles

cost(model: str | None = None, output=False) float#

Get the input cost of the message in USD.

format(oneline: bool = False, highlight: bool = False, max_length: int | None = None) str#

Format the message for display.

Parameters:
  • oneline – Whether to format the message as a single line

  • highlight – Whether to highlight code blocks

  • max_length – Maximum length of the message. If None, no truncation is applied. If set, will truncate at first newline or max_length, whichever comes first.

classmethod from_toml(toml: str) Self#

Converts a TOML string to a message.

The string can be a single [[message]].

get_codeblocks() list[Codeblock]#

Get all codeblocks from the message content.

replace(**kwargs) Self#

Replace attributes of the message.

to_dict(keys=None) dict#

Return a dict representation of the message, serializable to JSON.

to_toml() str#

Converts a message to a TOML string, for easy editing by hand in editor to then be parsed back.

to_xml() str#

Converts a message to an XML string with proper escaping.

Codeblock#

A codeblock in a message, possibly executable by tools.

class gptme.codeblock.Codeblock#

Codeblock(lang: str, content: str, path: str | None = None, start: int | None = None, fence: str = <factory>)

__init__(lang: str, content: str, path: str | None = None, start: int | None = None, fence: str = <factory>) None#
classmethod from_xml(content: str) Codeblock#

Example

<codeblock lang=”python” path=”example.py”> print(“Hello, world!”) </codeblock>

classmethod iter_from_markdown(markdown: str, streaming: bool = False) list[Codeblock]#

Extract codeblocks from markdown.

Note: Tracing removed from this function as it’s called hundreds of times per conversation, creating ~97% of all trace spans (see Issue #199).

to_xml() str#

Converts codeblock to XML with proper escaping.

LogManager#

Holds the current conversation as a list of messages, saves and loads the conversation to and from files, supports branching, etc.

Conversation log management for gptme.

Split into focused modules:

  • manager: Log data structure, LogManager orchestrator, message processing

  • conversations: ConversationMeta, conversation querying and management

class gptme.logmanager.ConversationMeta#

Metadata about a conversation.

__init__(id: str, name: str, path: str, created: float, modified: float, messages: int, branches: int, workspace: str, agent_name: str | None = None, agent_path: str | None = None, agent_avatar: str | None = None, agent_urls: dict[str, str] | None = None, model: str | None = None, total_cost: float = 0.0, total_input_tokens: int = 0, total_output_tokens: int = 0, total_cache_read_tokens: int = 0, models_usage: dict[str, dict[str, ~typing.Any]] = <factory>, last_message_role: str | None = None, last_message_preview: str | None = None) None#
format(metadata=False) str#

Format conversation metadata for display.

class gptme.logmanager.Log#

Log(messages: list[gptme.message.Message] = <factory>, persisted: tuple[gptme.message.Message, …] = (), persisted_path: pathlib.Path | None = None, persisted_size: int | None = None)

__init__(messages: list[~gptme.message.Message] = <factory>, persisted: tuple[~gptme.message.Message, ...] = (), persisted_path: ~pathlib.Path | None = None, persisted_size: int | None = None) None#
property persisted_messages: int#

Number of messages known to be on disk already.

print(show_hidden: bool = False) int#

Prints the log to the console. Returns the number of messages shown.

class gptme.logmanager.LogManager#

Manages a conversation log.

Can be used as a context manager to ensure locks are properly released:
with LogManager.load(logdir) as manager:

# use manager pass

__init__(log: list[Message] | None = None, logdir: str | Path | None = None, branch: str | None = None, lock: bool = True, view: str | None = None)#
append(msg: Message) None#

Appends a message to the log, writes the log, prints the message.

When on a view branch, implements dual-write: - Appends to master branch (preserves full history) - Appends to current view (maintains compacted context)

branch(name: str) None#

Switches to a branch.

create_view(name: str, log: Log | list[Message]) None#

Create a new view branch with compacted content.

Parameters:
  • name – View name (e.g., ‘compacted-001’)

  • log – The compacted log to store

diff(branch: str) str | None#

Prints the diff between the current branch and another branch.

edit(new_log: Log | list[Message]) None#

Edits the log.

fork(name: str) None#

Copy the conversation folder to a new name.

classmethod get_current_log() LogManager | None#

Get the current LogManager instance for this context.

get_next_view_name() str#

Generate the next sequential view name.

classmethod load(logdir: str | Path, initial_msgs: list[Message] | None = None, branch: str = 'main', create: bool = False, lock: bool = True, **kwargs) LogManager#

Loads a conversation log.

property master_log: Log#

Get the master log (always the main branch, never compacted).

property name: str#

Get the user-friendly display name from ChatConfig, fallback to chat_id.

read_model_trace()#

Read the persisted ModelSelectionTrace from logdir, or None if absent.

Returns:

ModelSelectionTrace if model_selection_trace.json exists, else None.

snapshot_message_files(msgs: list[Message]) list[Message]#

Snapshot message attachments while preserving valid existing hashes.

switch_to_master() None#

Switch back to master (full uncompacted history).

switch_view(name: str) None#

Switch to a view branch.

Parameters:

name – View name to switch to

to_dict(branches=False) dict#

Returns a dict representation of the log.

undo(n: int = 1, quiet=False) None#

Removes the last message from the log.

property workspace: Path#

Path to workspace directory (resolves symlink if exists).

write(branches=True, sync=False) None#

Writes to the conversation log.

Parameters:
  • branches – Whether to write other branches

  • sync – If True, force fsync to ensure data is on disk

write_model_trace() Path | None#

Persist the active ModelSelectionTrace and return its path.

Called automatically by write(). No-op when no trace is active in the current context (e.g. tests or sessions that pre-date Phase 0).

gptme.logmanager.check_for_modifications(log: Log) bool#

Check if the most recent assistant message (since last user) has file modifications.

Only checks the last assistant message to prevent re-triggering pre-commit/autocommit when the agent responds to hook output (e.g. pre-commit failure) without making new file changes. Checking all assistant messages since the last user would cause infinite loops: the original save is still visible after the agent writes a text response to the failure.

gptme.logmanager.conversation_name_error(value: str) str | None#

Return a validation error for unsafe conversation names, if any.

gptme.logmanager.delete_conversation(conv_id: str) bool#

Delete a conversation by its ID.

Parameters:

conv_id – The conversation ID to delete

Returns:

True if deleted successfully, False if not found

Raises:

PermissionError – If the conversation directory cannot be deleted

gptme.logmanager.ephemeral_cache_boundary(msgs_after_pruning: list[Message]) int | None#

Return the index (in msgs_after_pruning) of the last message before the first surviving ephemeral message, or None if no ephemeral messages remain.

This is the stable boundary that should receive a cache breakpoint: it’s the last message in the non-ephemeral prefix that will remain stable across turns as the ephemeral block continues to expire.

gptme.logmanager.get_conversation_by_id(conv_id: str, *, detail: bool = True) ConversationMeta | None#

Get a conversation by its ID.

Uses direct path lookup (O(1) file access) rather than scanning all conversations.

Parameters:

conv_id – The conversation ID to find

Returns:

ConversationMeta if found, None otherwise

gptme.logmanager.get_conversation_meta_direct(conv_id: str, *, detail: bool = True, logs_dir: Path | None = None) ConversationMeta | None#

Get a single conversation’s metadata by direct path lookup.

Bypasses the full glob+stat scan used by get_conversations(). O(1) file access instead of O(N_conversations) — suitable for partial cache updates where only one conversation changed.

Returns None when the conversation directory or JSONL file does not exist.

gptme.logmanager.get_conversations(*, detail: bool = True, include_test: bool = True) Generator[ConversationMeta, None, None]#

Returns all conversations.

Parameters:
  • detail – If True (default), performs a full JSONL scan to compute exact costs, token counts, and model info. If False, reads only the tail of each file for a faster scan — suitable for list/search endpoints where cost/token aggregates are not displayed.

  • include_test – If True (default), includes test/eval conversations. If False, skips them before scanning any files or loading per-conversation config.

gptme.logmanager.get_user_conversations(*, detail: bool = True) Generator[ConversationMeta, None, None]#

Returns all user conversations, excluding ones used for testing, evals, etc.

gptme.logmanager.list_conversations(limit: int = 20, include_test: bool = False, *, detail: bool = True) list[ConversationMeta]#

List conversations with a limit.

Parameters:
  • limit – Maximum number of conversations to return

  • include_test – Whether to include test conversations

  • detail – If True, performs full JSONL scan for costs/tokens. If False, uses fast tail-only scan.

gptme.logmanager.prepare_messages(msgs: list[Message], workspace: Path | None = None, logdir: Path | None = None) list[Message]#

Prepares the messages before sending to the LLM. - Takes the stored gptme conversation log - Enhances it with context such as file contents - Transforms it to the format expected by LLM providers

gptme.logmanager.prune_ephemeral_messages(msgs: list[Message]) list[Message]#

Remove messages whose ephemeral_ttl has been exceeded.

ephemeral_ttl=N means: keep the message for N later assistant turns, then drop it. Walk backward counting assistant messages seen after each message; drop ephemeral messages once the count exceeds their TTL. Pinned messages are never dropped regardless of TTL.

gptme.logmanager.rename_conversation(conv_id: str, new_name: str) bool#

Rename a conversation by updating its display name in the chat config.

Parameters:
  • conv_id – The conversation ID to rename

  • new_name – The new display name for the conversation

Returns:

True if renamed successfully, False if not found