Skip to content

Session hooks and extraction

retold.ingest.session

The session transcript: a process-local cache over persisted turns, and the adapter-facing hooks.

SessionBuffer

Read session turns efficiently while keeping writes durable in the store.

Call append_turn on this buffer, rather than on its store, whenever the cache is in use.

turns

turns(session_id: str | None) -> list[Turn]

Return a detached transcript in turn order, or no turns for a missing session ID.

append_turn

append_turn(turn: Turn) -> None

Persist a turn and invalidate the matching cached transcript.

invalidate

invalidate(session_id: str) -> None

Drop the cached transcript so the next read sees turns another process or thread appended.

SessionHooks

LLD 12: the three calls an adapter makes, plus the idle-timeout split for hosts without a session end.

A split closes the idle session, hands it to on_end for extraction, and continues under a derived session id. Evidence quoted from the earlier segment is then unfindable from the new one, so such a write is downgraded by the ordinary evidence rule rather than lost; a cross-session lookback is an LLD 18 item and is not built here.

on_session_start

on_session_start(
    principal: Principal, *, at: datetime | None = None
) -> None

Create the session for the derived principal; a repeated start is a no-op.

on_turn

on_turn(
    principal: Principal,
    role: TurnRole,
    content: str,
    *,
    at: datetime | None = None,
) -> Principal

Append one numbered turn and return the principal to keep using, split if the session went idle.

on_session_end

on_session_end(
    principal: Principal, *, at: datetime | None = None
) -> Thread | None

Mark the session complete and return the extraction thread the callback started, if any.

retold.ingest.ingestor

The explicit, evidence-backed memory write path.

WriteRequest dataclass

The validated values accepted by the framework-neutral memory write path.

SummaryRequest dataclass

One session summary: the extractor's text plus the session it describes.

WriteResult dataclass

The durable outcome, lifecycle status, audit note, and timings of one write.

EntityAmbiguityCandidate dataclass

One readable entity returned to let a caller resolve an ambiguous mention safely.

Ingestor

Write explicit memories with evidence, lifecycle checks, durable indexes, and audit events.

write

write(
    principal: Principal, request: WriteRequest
) -> WriteResult

Validate and persist one explicit memory or return its safe non-write outcome.

revise

revise(
    principal: Principal,
    record_id: str,
    action: Literal["confirm", "supersede", "expire"],
    reason: str,
    *,
    content: str | None = None,
    source_kind: SourceKind = "agent_inference",
    evidence: str | None = None,
    evidence_turn: int | None = None,
) -> WriteResult

Apply a user-authorized lifecycle revision after checking scope, status, and supersession authority.

write_session_summary

write_session_summary(
    principal: Principal, request: SummaryRequest
) -> WriteResult

Write the one live summary for a session, replacing an earlier summary whose text changed.

The summary is keyed by source_ref = "session:<id>", not by neighbour similarity. A re-run with identical text changes nothing; changed text supersedes the earlier summary so its history remains. Mention-role entities are linked when they resolve unambiguously and dropped otherwise.

retold.ingest.extraction

Session extraction: claim, extract, validate, review, revalidate, write, summarise, finish.

The order is the safety argument. A session is claimed atomically before any model call, so two workers cannot both process it. The extractor and the reviewer run before anything is written, so a failure in either writes nothing and leaves the claim to expire. Accepted candidates then go through the same ingestor as an explicit memory_write; the extractor gets no shortcut past evidence, entity, duplicate, or supersession rules. Activation is decided afterwards by the activation service, which is a separate decision from whether the candidate was worth keeping.

CandidateOutcome dataclass

What happened to one proposed candidate, and at which stage.

ExtractionRunner

Run the asynchronous write path for one finished session.

schedule

schedule(
    session_id: str,
    principal: Principal,
    *,
    force: bool = False,
) -> Thread

Run extract_session on a daemon thread; the host continues without waiting.

extract_session

extract_session(
    session_id: str,
    principal: Principal,
    *,
    force: bool = False,
) -> ExtractionResult

LLD 8.2 steps 1 to 8. Refuses without an event when another worker holds the session.

retold.ingest.extractor

The extractor: a finished transcript in, candidate records and one session summary out.

The extractor proposes; it never writes. Its output is validated, reviewed, and revalidated by extraction.py before anything reaches the ingestor. The prompt lives beside this module and its version is recorded on every extraction.run event, so a prompt change is tracked like an embedding-model change.

ExtractionError

Bases: RuntimeError

The extractor could not produce a usable ExtractionOutput; the run fails closed.

FakeExtractor

Return a canned output, run a script, or raise; records every call for assertions.

StructuredLLMExtractor

Call a hosted model with the versioned prompt and parse its JSON into ExtractionOutput.

load_prompt

load_prompt(name: str) -> str

Read one versioned prompt shipped with the package.

render_request

render_request(
    turns: Sequence[Turn], context: ExtractionContext
) -> str

Number the transcript and attach the bounded context the prompt describes.

parse_extraction_output

parse_extraction_output(
    payload: dict[str, Any], *, max_candidates: int
) -> ExtractionOutput

Validate the model's JSON strictly; anything outside the contract is a malformed output.

dump_candidate

dump_candidate(
    candidate: CandidateRecord,
) -> dict[str, Any]

Serialise a candidate for prompts and audit payloads.

retold.ingest.reviewer

Review before apply: an independent judgement on each extracted candidate.

The reviewer sees the candidate, its evidence turn with the turns around it, and the live records about the same subject in the destination scope. It accepts, rejects, or narrows. check_revision enforces what a revision may touch: content, the attribute hint, temporal metadata, and confidence, each only in the narrowing direction. Source authority, the quote and its turn, the scope, the entity mentions, and the event time are outside its reach; the extraction runner rejects any revision that changes them.

ReviewError

Bases: RuntimeError

The reviewer failed or answered outside its contract; the whole extraction run fails closed.

TableReviewer

Decide by candidate content from a table, or by a script; accept anything unlisted.

StructuredLLMReviewer

Ask a hosted model for accept, reject, or a narrowing revision, using the versioned prompt.

check_revision

check_revision(
    original: CandidateRecord, revised: CandidateRecord
) -> str | None

Return why a revision is invalid, or None when it only narrows the candidate.

adjacent_turns

adjacent_turns(
    turns: Sequence[Turn], turn_number: int
) -> list[Turn]

The turns immediately before and after turn_number.