Skip to content

API Reference

This reference is generated from the public modules in the current package. RFCs may describe planned behavior that is not part of this API.

Composition

Explicit public composition root for PowerContext components.

Artifacts

Bases: BaseModel

Compose shared Artifact persistence and read operations.

Source code in src/powercontext/context.py
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
class Artifacts(BaseModel):
    """Compose shared Artifact persistence and read operations."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    catalog: ArtifactCatalog[Artifact[object]]
    store: ArtifactStore[ArtifactDraft[object], Artifact[object]]

    async def add(self, draft: ArtifactDraft[object], /) -> Artifact[object]:
        """Commit an initial Artifact Revision without semantic generation."""

        return await self.store.add(draft)

    async def revise(
        self,
        artifact: Artifact[object],
        draft: ArtifactDraft[object],
        /,
    ) -> Artifact[object]:
        """Commit a Revision using ``artifact`` as the optimistic base."""

        if artifact.family != draft.family:
            raise ArtifactFamilyMismatchError(artifact, draft)
        return await self.store.revise(artifact, draft)

    async def get(self, artifact: Artifact[object], /) -> Artifact[object]:
        """Return the canonical exact Revision matching ``artifact``."""

        return await self.catalog.get(artifact)

    async def latest(self, artifact: Artifact[object], /) -> Artifact[object]:
        """Return the latest visible Revision of ``artifact``."""

        return await self.catalog.latest(artifact)

    async def revisions(self, artifact: Artifact[object], /) -> tuple[Artifact[object], ...]:
        """Return the visible history of ``artifact``."""

        return await self.catalog.revisions(artifact)

add(draft) async

Commit an initial Artifact Revision without semantic generation.

Source code in src/powercontext/context.py
73
74
75
76
async def add(self, draft: ArtifactDraft[object], /) -> Artifact[object]:
    """Commit an initial Artifact Revision without semantic generation."""

    return await self.store.add(draft)

get(artifact) async

Return the canonical exact Revision matching artifact.

Source code in src/powercontext/context.py
90
91
92
93
async def get(self, artifact: Artifact[object], /) -> Artifact[object]:
    """Return the canonical exact Revision matching ``artifact``."""

    return await self.catalog.get(artifact)

latest(artifact) async

Return the latest visible Revision of artifact.

Source code in src/powercontext/context.py
95
96
97
98
async def latest(self, artifact: Artifact[object], /) -> Artifact[object]:
    """Return the latest visible Revision of ``artifact``."""

    return await self.catalog.latest(artifact)

revise(artifact, draft) async

Commit a Revision using artifact as the optimistic base.

Source code in src/powercontext/context.py
78
79
80
81
82
83
84
85
86
87
88
async def revise(
    self,
    artifact: Artifact[object],
    draft: ArtifactDraft[object],
    /,
) -> Artifact[object]:
    """Commit a Revision using ``artifact`` as the optimistic base."""

    if artifact.family != draft.family:
        raise ArtifactFamilyMismatchError(artifact, draft)
    return await self.store.revise(artifact, draft)

revisions(artifact) async

Return the visible history of artifact.

Source code in src/powercontext/context.py
100
101
102
103
async def revisions(self, artifact: Artifact[object], /) -> tuple[Artifact[object], ...]:
    """Return the visible history of ``artifact``."""

    return await self.catalog.revisions(artifact)

PowerContext

Bases: BaseModel, Generic[SourcesT, ArtifactsT, TriggersT]

Bind three explicitly selected component groups without owning their lifecycle.

Source code in src/powercontext/context.py
18
19
20
21
22
23
24
25
class PowerContext(BaseModel, Generic[SourcesT, ArtifactsT, TriggersT]):
    """Bind three explicitly selected component groups without owning their lifecycle."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    sources: SourcesT
    artifacts: ArtifactsT
    triggers: TriggersT

Sources

Bases: BaseModel

Compose Source acquisition, persistence, and read operations.

Source code in src/powercontext/context.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Sources(BaseModel):
    """Compose Source acquisition, persistence, and read operations."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    catalog: SourceCatalog
    store: SourceStore[Source]

    async def resolve(self, value: object, /) -> Source:
        """Resolve an adapter-native input without persisting it."""

        return await self.catalog.resolve(value)

    async def add(self, source: Source, /) -> Source:
        """Persist a resolved Source without deriving Artifacts."""

        self.catalog.as_ref(source)
        stored = await self.store.add(source)
        self.catalog.as_ref(stored)
        return stored

    async def get(self, source: Source, /) -> Source:
        """Return the canonical persisted Source matching ``source``."""

        return await self.catalog.get(source)

    async def list(self) -> tuple[Source, ...]:
        """Return the Sources visible in the current catalog view."""

        return await self.catalog.list()

    async def read(self, source: Source, /) -> object:
        """Read the adapter-native value described by ``source``."""

        return await self.catalog.read(source)

add(source) async

Persist a resolved Source without deriving Artifacts.

Source code in src/powercontext/context.py
41
42
43
44
45
46
47
async def add(self, source: Source, /) -> Source:
    """Persist a resolved Source without deriving Artifacts."""

    self.catalog.as_ref(source)
    stored = await self.store.add(source)
    self.catalog.as_ref(stored)
    return stored

get(source) async

Return the canonical persisted Source matching source.

Source code in src/powercontext/context.py
49
50
51
52
async def get(self, source: Source, /) -> Source:
    """Return the canonical persisted Source matching ``source``."""

    return await self.catalog.get(source)

list() async

Return the Sources visible in the current catalog view.

Source code in src/powercontext/context.py
54
55
56
57
async def list(self) -> tuple[Source, ...]:
    """Return the Sources visible in the current catalog view."""

    return await self.catalog.list()

read(source) async

Read the adapter-native value described by source.

Source code in src/powercontext/context.py
59
60
61
62
async def read(self, source: Source, /) -> object:
    """Read the adapter-native value described by ``source``."""

    return await self.catalog.read(source)

resolve(value) async

Resolve an adapter-native input without persisting it.

Source code in src/powercontext/context.py
36
37
38
39
async def resolve(self, value: object, /) -> Source:
    """Resolve an adapter-native input without persisting it."""

    return await self.catalog.resolve(value)

Sources

Source

Bases: BaseModel

Base value for an adapter-owned Source description.

Source code in src/powercontext/sources/models.py
31
32
33
34
35
36
class Source(BaseModel):
    """Base value for an adapter-owned Source description."""

    name: str
    materialization: SourceMaterialization
    description: str | None = None

SourceAdapter

Bases: Protocol[InputT, SourceT, ValueT_co]

Resolve one exact input class and read one concrete Source class.

Source code in src/powercontext/sources/adapters.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class SourceAdapter(Protocol[InputT, SourceT, ValueT_co]):
    """Resolve one exact input class and read one concrete Source class."""

    input_class: type[InputT]
    """The exact input class accepted by this adapter."""

    name: str
    """The stable registration name for this adapter."""

    source_class: type[SourceT]
    """The exact Source class produced and read by this adapter."""

    async def resolve(self, value: InputT, /) -> SourceT:
        """Resolve an adapter-native value without changing a catalog."""

        ...

    async def read(self, source: SourceT, /) -> ValueT_co:
        """Read the adapter-native value described by a Source."""

        ...

input_class instance-attribute

The exact input class accepted by this adapter.

name instance-attribute

The stable registration name for this adapter.

source_class instance-attribute

The exact Source class produced and read by this adapter.

read(source) async

Read the adapter-native value described by a Source.

Source code in src/powercontext/sources/adapters.py
29
30
31
32
async def read(self, source: SourceT, /) -> ValueT_co:
    """Read the adapter-native value described by a Source."""

    ...

resolve(value) async

Resolve an adapter-native value without changing a catalog.

Source code in src/powercontext/sources/adapters.py
24
25
26
27
async def resolve(self, value: InputT, /) -> SourceT:
    """Resolve an adapter-native value without changing a catalog."""

    ...

SourceCatalog

A read-only Source catalog routed by actual adapters.

Source code in src/powercontext/sources/catalog.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class SourceCatalog:
    """A read-only Source catalog routed by actual adapters."""

    def __init__(
        self,
        *,
        backend: SourceCatalogBackend,
        adapters: Iterable[_AnySourceAdapter],
    ) -> None:
        by_input: dict[type[object], _AnySourceAdapter] = {}
        by_source: dict[type[Source], _AnySourceAdapter] = {}
        for adapter in adapters:
            input_class, source_class = _validate_adapter(adapter)
            if input_class in by_input:
                raise SourceConflictError("input_class", input_class)
            if source_class in by_source:
                raise SourceConflictError("source_class", source_class)
            by_input[input_class] = adapter
            by_source[source_class] = adapter
        self._backend = backend
        self._by_input = by_input
        self._by_source = by_source

    async def list(self) -> tuple[Source, ...]:
        sources = await self._backend.list()
        for source in sources:
            self.as_ref(source)
        return sources

    async def get(self, source: Source, /) -> Source:
        self.as_ref(source)
        stored = await self._backend.get(source)
        self.as_ref(stored)
        if type(stored) is not type(source) or stored != source:
            raise SourceNotFoundError(source)
        return stored

    def as_ref(self, source: Source, /) -> SourceRef:
        adapter = _adapter_for_source(source, self._by_source)
        return SourceRef(source_type=adapter.name, source_id=source.name)

    async def resolve(self, value: object, /) -> Source:
        input_class = type(value)
        try:
            adapter = self._by_input[input_class]
        except KeyError:
            raise SourceAdapterNotFoundError("input", input_class) from None
        source = await adapter.resolve(value)
        if type(source) is not adapter.source_class:
            raise InvalidSourceResultError(adapter.name, "resolve", adapter.source_class, type(source))
        self.as_ref(source)
        return cast(Source, source)

    async def read(self, source: Source, /) -> object:
        adapter = _adapter_for_source(source, self._by_source)
        return await adapter.read(source)

SourceCatalogBackend

Bases: Protocol

Provide the Source reads required by SourceCatalog.

Source code in src/powercontext/sources/protocols.py
17
18
19
20
21
22
23
24
25
26
@runtime_checkable
class SourceCatalogBackend(Protocol):
    """Provide the Source reads required by SourceCatalog."""

    async def get(self, source: Source, /) -> Source: ...

    async def list(self) -> tuple[Source, ...]:
        """Return the Sources visible in one backend view."""

        ...

list() async

Return the Sources visible in one backend view.

Source code in src/powercontext/sources/protocols.py
23
24
25
26
async def list(self) -> tuple[Source, ...]:
    """Return the Sources visible in one backend view."""

    ...

SourceMaterialization

Bases: StrEnum

Describe where the value read for a Source comes from.

Source code in src/powercontext/sources/models.py
11
12
13
14
15
class SourceMaterialization(StrEnum):
    """Describe where the value read for a Source comes from."""

    CAPTURED = "captured"
    REFERENCED = "referenced"

SourceRef

Bases: BaseModel

A stable reference to one Source in the current catalog view.

Source code in src/powercontext/sources/models.py
18
19
20
21
22
23
24
25
26
27
28
class SourceRef(BaseModel):
    """A stable reference to one Source in the current catalog view."""

    source_type: str
    source_id: str

    @field_validator("source_type", "source_id")
    @classmethod
    def validate_reference_part(cls, value: str, info) -> str:
        _validate_reference_part(info.field_name, value)
        return value

SourceStore

Bases: Protocol[SourceT]

Persist resolved Sources for later catalog reads and lineage.

Source code in src/powercontext/sources/protocols.py
10
11
12
13
14
@runtime_checkable
class SourceStore(Protocol[SourceT]):
    """Persist resolved Sources for later catalog reads and lineage."""

    async def add(self, value: SourceT, /) -> SourceT: ...

Artifacts

Immutable artifacts and their read-only catalog contract.

Artifact

Bases: BaseModel, Generic[ContentT]

An immutable snapshot in an artifact lifecycle.

Source code in src/powercontext/artifacts/models.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Artifact(BaseModel, Generic[ContentT]):
    """An immutable snapshot in an artifact lifecycle."""

    family: ClassVar[str] = "artifact"

    artifact_id: str
    revision: StrictInt = Field(ge=1)
    content: ContentT
    lineage: ArtifactLineage = Field(default_factory=ArtifactLineage)

    @field_validator("artifact_id")
    @classmethod
    def validate_artifact_id(cls, value: str) -> str:
        _validate_reference_part("artifact_id", value)
        if len(value) > MAX_ARTIFACT_ID_LENGTH:
            raise InvalidArtifactReferenceError(
                "artifact_id",
                f"must not exceed {MAX_ARTIFACT_ID_LENGTH} characters",
            )
        return value

    def as_ref(self) -> ArtifactRef:
        """Return an exact reference to this revision."""

        return ArtifactRef(family=self.family, artifact_id=self.artifact_id, revision=self.revision)

as_ref()

Return an exact reference to this revision.

Source code in src/powercontext/artifacts/models.py
76
77
78
79
def as_ref(self) -> ArtifactRef:
    """Return an exact reference to this revision."""

    return ArtifactRef(family=self.family, artifact_id=self.artifact_id, revision=self.revision)

ArtifactCatalog

Bases: Protocol[ArtifactT]

Read artifact revisions without owning their writes.

Source code in src/powercontext/artifacts/protocols.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@runtime_checkable
class ArtifactCatalog(Protocol[ArtifactT]):
    """Read artifact revisions without owning their writes."""

    async def get(self, artifact: ArtifactT, /) -> ArtifactT:
        """Return the canonical exact revision matching ``artifact``."""

        ...

    async def latest(self, artifact: ArtifactT, /) -> ArtifactT:
        """Return the latest visible revision of ``artifact``."""

        ...

    async def revisions(self, artifact: ArtifactT, /) -> tuple[ArtifactT, ...]:
        """Return the visible history of ``artifact`` in ascending revision order."""

        ...

get(artifact) async

Return the canonical exact revision matching artifact.

Source code in src/powercontext/artifacts/protocols.py
17
18
19
20
async def get(self, artifact: ArtifactT, /) -> ArtifactT:
    """Return the canonical exact revision matching ``artifact``."""

    ...

latest(artifact) async

Return the latest visible revision of artifact.

Source code in src/powercontext/artifacts/protocols.py
22
23
24
25
async def latest(self, artifact: ArtifactT, /) -> ArtifactT:
    """Return the latest visible revision of ``artifact``."""

    ...

revisions(artifact) async

Return the visible history of artifact in ascending revision order.

Source code in src/powercontext/artifacts/protocols.py
27
28
29
30
async def revisions(self, artifact: ArtifactT, /) -> tuple[ArtifactT, ...]:
    """Return the visible history of ``artifact`` in ascending revision order."""

    ...

ArtifactDraft

Bases: BaseModel, Generic[ContentT]

Content and complete evidence supplied for one Artifact write.

Source code in src/powercontext/artifacts/models.py
40
41
42
43
44
45
46
47
48
49
50
51
52
class ArtifactDraft(BaseModel, Generic[ContentT]):
    """Content and complete evidence supplied for one Artifact write."""

    family: ClassVar[str] = "artifact"

    content: ContentT
    sources: tuple[SourceRef, ...] = ()
    artifacts: tuple[ArtifactRef, ...] = ()

    @model_validator(mode="after")
    def validate_family(self):
        _validate_reference_part("family", self.family)
        return self

ArtifactLineage

Bases: BaseModel

The direct evidence used to produce one artifact revision.

Source code in src/powercontext/artifacts/models.py
33
34
35
36
37
class ArtifactLineage(BaseModel):
    """The direct evidence used to produce one artifact revision."""

    sources: tuple[SourceRef, ...] = ()
    artifacts: tuple[ArtifactRef, ...] = ()

ArtifactRef

Bases: BaseModel

A stable reference to one exact artifact revision.

Source code in src/powercontext/artifacts/models.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ArtifactRef(BaseModel):
    """A stable reference to one exact artifact revision."""

    family: str
    artifact_id: str
    revision: StrictInt = Field(ge=1)

    @field_validator("family", "artifact_id")
    @classmethod
    def validate_identity(cls, value: str, info) -> str:
        _validate_reference_part(info.field_name, value)
        maximum = MAX_ARTIFACT_FAMILY_LENGTH if info.field_name == "family" else MAX_ARTIFACT_ID_LENGTH
        if len(value) > maximum:
            raise InvalidArtifactReferenceError(info.field_name, f"must not exceed {maximum} characters")
        return value

ArtifactStore

Bases: Protocol[DraftT_contra, ArtifactT]

Commit new Artifact revisions from complete domain objects.

Source code in src/powercontext/artifacts/protocols.py
33
34
35
36
37
38
39
40
41
42
43
44
45
@runtime_checkable
class ArtifactStore(Protocol[DraftT_contra, ArtifactT]):
    """Commit new Artifact revisions from complete domain objects."""

    async def add(self, draft: DraftT_contra, /) -> ArtifactT:
        """Commit the first revision represented by ``draft``."""

        ...

    async def revise(self, artifact: ArtifactT, draft: DraftT_contra, /) -> ArtifactT:
        """Commit ``draft`` only if ``artifact`` remains the latest revision."""

        ...

add(draft) async

Commit the first revision represented by draft.

Source code in src/powercontext/artifacts/protocols.py
37
38
39
40
async def add(self, draft: DraftT_contra, /) -> ArtifactT:
    """Commit the first revision represented by ``draft``."""

    ...

revise(artifact, draft) async

Commit draft only if artifact remains the latest revision.

Source code in src/powercontext/artifacts/protocols.py
42
43
44
45
async def revise(self, artifact: ArtifactT, draft: DraftT_contra, /) -> ArtifactT:
    """Commit ``draft`` only if ``artifact`` remains the latest revision."""

    ...

Memory

Artifact-native Memory domain and service contracts.

CandidatePipeline

Bases: Protocol

Produce untrusted Memory candidates from canonical bounded evidence.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
79
80
81
82
83
84
85
class CandidatePipeline(Protocol):
    """Produce untrusted Memory candidates from canonical bounded evidence."""

    async def extract(self, request: MemoryCandidateRequest, /) -> tuple[MemoryEntryInput, ...]:
        """Return candidates that still require all service validations."""

        ...

extract(request) async

Return candidates that still require all service validations.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
82
83
84
85
async def extract(self, request: MemoryCandidateRequest, /) -> tuple[MemoryEntryInput, ...]:
    """Return candidates that still require all service validations."""

    ...

DefaultMemoryEvidenceProjector

Expose only stable public evidence metadata unless a caller opts in to more.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
class DefaultMemoryEvidenceProjector:
    """Expose only stable public evidence metadata unless a caller opts in to more."""

    def project_source(self, source: Source, /) -> JsonValue:
        return _validated_json({
            "name": source.name,
            "materialization": source.materialization.value,
            "description": source.description,
        })

    def project_artifact(self, artifact: Artifact[object], /) -> JsonValue:
        return _validated_json({
            "artifact_id": artifact.artifact_id,
            "revision": artifact.revision,
            "family": artifact.family,
            "content": artifact.content,
        })

EmbeddingProfile

Bases: BaseModel

The Memory embedding index contract for one deployment.

Source code in src/powercontext/builtin/artifacts/memory/models.py
20
21
22
23
24
25
26
27
class EmbeddingProfile(BaseModel):
    """The Memory embedding index contract for one deployment."""

    profile_id: str
    model: str
    dimension: int
    distance: Literal["l2"] = "l2"
    normalization: Literal["none", "unit"] = "unit"

LLMMemoryCandidatePipeline

Map schema-valid model proposals back to exact bounded domain values.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class LLMMemoryCandidatePipeline:
    """Map schema-valid model proposals back to exact bounded domain values."""

    def __init__(
        self,
        generator: StructuredGenerator[MemoryExtractionInput, MemoryExtractionOutput],
        *,
        evidence_projector: MemoryEvidenceProjector | None = None,
    ) -> None:
        self._generator = generator
        self._evidence_projector = (
            DefaultMemoryEvidenceProjector() if evidence_projector is None else evidence_projector
        )

    async def extract(self, request: MemoryCandidateRequest, /) -> tuple[MemoryEntryInput, ...]:
        """Generate and map candidates that still require MemoryService validation."""

        extraction_input, evidence = _extraction_input(request, self._evidence_projector)
        result = await self._generator.generate(extraction_input)
        output = _validated_output(result)
        current_entries = {entry.entry_id: entry for entry in request.current_entries}
        revised_entries: set[str] = set()
        candidates: list[MemoryEntryInput] = []
        for proposal in output.candidates:
            selected_sources, selected_artifacts = _selected_evidence(proposal, evidence)
            entry = _revision_target(proposal, current_entries, revised_entries)
            candidates.append(
                MemoryEntryInput(
                    entry=entry,
                    kind=proposal.kind,
                    text=proposal.text,
                    sources=selected_sources,
                    artifacts=selected_artifacts,
                    reason=proposal.reason,
                )
            )
        return tuple(candidates)

extract(request) async

Generate and map candidates that still require MemoryService validation.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
async def extract(self, request: MemoryCandidateRequest, /) -> tuple[MemoryEntryInput, ...]:
    """Generate and map candidates that still require MemoryService validation."""

    extraction_input, evidence = _extraction_input(request, self._evidence_projector)
    result = await self._generator.generate(extraction_input)
    output = _validated_output(result)
    current_entries = {entry.entry_id: entry for entry in request.current_entries}
    revised_entries: set[str] = set()
    candidates: list[MemoryEntryInput] = []
    for proposal in output.candidates:
        selected_sources, selected_artifacts = _selected_evidence(proposal, evidence)
        entry = _revision_target(proposal, current_entries, revised_entries)
        candidates.append(
            MemoryEntryInput(
                entry=entry,
                kind=proposal.kind,
                text=proposal.text,
                sources=selected_sources,
                artifacts=selected_artifacts,
                reason=proposal.reason,
            )
        )
    return tuple(candidates)

LLMMemoryReranker

Use one structured listwise generation request to select Memory hits.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class LLMMemoryReranker:
    """Use one structured listwise generation request to select Memory hits."""

    policy_id = MEMORY_RERANK_INSTRUCTIONS_VERSION

    def __init__(
        self,
        generator: StructuredGenerator[MemoryRerankInput, MemoryRerankOutput],
        /,
    ) -> None:
        self._generator = generator

    async def rerank(
        self,
        query: str,
        candidates: tuple[MemoryHit, ...],
        limit: int,
        /,
    ) -> MemoryRerankDecision:
        """Generate and normalize a sparse selection from the coarse ranks."""

        _validate_bounds(len(candidates), limit)
        result = await self._generator.generate(
            MemoryRerankInput(
                query=query,
                max_results=limit,
                candidates=tuple(
                    MemoryRerankCandidate(rank=rank, text=candidate.text)
                    for rank, candidate in enumerate(candidates, start=1)
                ),
            )
        )
        selected_ranks, discarded_rank_count = _normalize_ranks(
            result.output.selected_ranks,
            candidate_count=len(candidates),
            limit=limit,
        )
        if selected_ranks:
            return MemoryRerankDecision(
                selected_ranks=selected_ranks,
                usage=result.usage,
                discarded_rank_count=discarded_rank_count,
            )
        return MemoryRerankDecision(
            selected_ranks=tuple(range(1, limit + 1)),
            usage=result.usage,
            discarded_rank_count=discarded_rank_count,
            used_fallback=True,
        )

rerank(query, candidates, limit) async

Generate and normalize a sparse selection from the coarse ranks.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
async def rerank(
    self,
    query: str,
    candidates: tuple[MemoryHit, ...],
    limit: int,
    /,
) -> MemoryRerankDecision:
    """Generate and normalize a sparse selection from the coarse ranks."""

    _validate_bounds(len(candidates), limit)
    result = await self._generator.generate(
        MemoryRerankInput(
            query=query,
            max_results=limit,
            candidates=tuple(
                MemoryRerankCandidate(rank=rank, text=candidate.text)
                for rank, candidate in enumerate(candidates, start=1)
            ),
        )
    )
    selected_ranks, discarded_rank_count = _normalize_ranks(
        result.output.selected_ranks,
        candidate_count=len(candidates),
        limit=limit,
    )
    if selected_ranks:
        return MemoryRerankDecision(
            selected_ranks=selected_ranks,
            usage=result.usage,
            discarded_rank_count=discarded_rank_count,
        )
    return MemoryRerankDecision(
        selected_ranks=tuple(range(1, limit + 1)),
        usage=result.usage,
        discarded_rank_count=discarded_rank_count,
        used_fallback=True,
    )

Memory

Bases: Artifact[MemoryContent]

An immutable snapshot in a Memory lifecycle.

Source code in src/powercontext/builtin/artifacts/memory/models.py
76
77
78
79
class Memory(Artifact[MemoryContent]):
    """An immutable snapshot in a Memory lifecycle."""

    family: ClassVar[str] = "memory"

MemoryBackend

Bases: Protocol

Storage and retrieval capabilities required by the Memory Family.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class MemoryBackend(Protocol):
    """Storage and retrieval capabilities required by the Memory Family."""

    async def capabilities(self) -> MemoryCapabilities:
        """Return probed deployment capabilities."""

        ...

    async def get(self, memory: ArtifactRef, /) -> Memory:
        """Load one exact Memory Revision."""

        ...

    async def latest(self, artifact_id: str, /) -> Memory:
        """Load the current head of one Memory identity."""

        ...

    async def entries(self, memory: ArtifactRef, /) -> tuple[MemoryEntryVersion, ...]:
        """Load the entry versions referenced by an exact manifest."""

        ...

    async def projections(self, memory: ArtifactRef, /) -> tuple[MemoryProjection, ...]:
        """Load rebuildable active-head projections for an exact Memory Revision."""

        ...

    async def rebuild_projections(self, embedding_model: EmbeddingModel | None = None, /) -> None:
        """Rebuild active-head and search projections from authoritative revisions."""

        ...

    def begin(self) -> AbstractAsyncContextManager[MemoryUnitOfWork]:
        """Open the adapter-specific atomic write boundary."""

        ...

    async def changes(
        self,
        memory: ArtifactRef,
        since_revision: int | None,
        /,
    ) -> tuple[MemoryRevisionChanges, ...]:
        """Read compact Revision changes without entry bodies."""

        ...

    async def vector_complete(
        self,
        memories: tuple[ArtifactRef, ...],
        profile: EmbeddingProfile,
        /,
    ) -> bool:
        """Derive fixed-profile vector completeness for selected heads."""

        ...

    async def search(self, request: MemorySearchRequest, /) -> MemorySearchChannels:
        """Return backend-ordered FTS/vector channels after manifest checks."""

        ...

    async def expand(self, hits: tuple[MemoryHit, ...], /) -> tuple[MemoryEntryVersion, ...]:
        """Load and validate exact versions anchored by hits."""

        ...

begin()

Open the adapter-specific atomic write boundary.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
130
131
132
133
def begin(self) -> AbstractAsyncContextManager[MemoryUnitOfWork]:
    """Open the adapter-specific atomic write boundary."""

    ...

capabilities() async

Return probed deployment capabilities.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
100
101
102
103
async def capabilities(self) -> MemoryCapabilities:
    """Return probed deployment capabilities."""

    ...

changes(memory, since_revision) async

Read compact Revision changes without entry bodies.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
135
136
137
138
139
140
141
142
143
async def changes(
    self,
    memory: ArtifactRef,
    since_revision: int | None,
    /,
) -> tuple[MemoryRevisionChanges, ...]:
    """Read compact Revision changes without entry bodies."""

    ...

entries(memory) async

Load the entry versions referenced by an exact manifest.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
115
116
117
118
async def entries(self, memory: ArtifactRef, /) -> tuple[MemoryEntryVersion, ...]:
    """Load the entry versions referenced by an exact manifest."""

    ...

expand(hits) async

Load and validate exact versions anchored by hits.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
160
161
162
163
async def expand(self, hits: tuple[MemoryHit, ...], /) -> tuple[MemoryEntryVersion, ...]:
    """Load and validate exact versions anchored by hits."""

    ...

get(memory) async

Load one exact Memory Revision.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
105
106
107
108
async def get(self, memory: ArtifactRef, /) -> Memory:
    """Load one exact Memory Revision."""

    ...

latest(artifact_id) async

Load the current head of one Memory identity.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
110
111
112
113
async def latest(self, artifact_id: str, /) -> Memory:
    """Load the current head of one Memory identity."""

    ...

projections(memory) async

Load rebuildable active-head projections for an exact Memory Revision.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
120
121
122
123
async def projections(self, memory: ArtifactRef, /) -> tuple[MemoryProjection, ...]:
    """Load rebuildable active-head projections for an exact Memory Revision."""

    ...

rebuild_projections(embedding_model=None) async

Rebuild active-head and search projections from authoritative revisions.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
125
126
127
128
async def rebuild_projections(self, embedding_model: EmbeddingModel | None = None, /) -> None:
    """Rebuild active-head and search projections from authoritative revisions."""

    ...

search(request) async

Return backend-ordered FTS/vector channels after manifest checks.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
155
156
157
158
async def search(self, request: MemorySearchRequest, /) -> MemorySearchChannels:
    """Return backend-ordered FTS/vector channels after manifest checks."""

    ...

vector_complete(memories, profile) async

Derive fixed-profile vector completeness for selected heads.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
145
146
147
148
149
150
151
152
153
async def vector_complete(
    self,
    memories: tuple[ArtifactRef, ...],
    profile: EmbeddingProfile,
    /,
) -> bool:
    """Derive fixed-profile vector completeness for selected heads."""

    ...

MemoryBackendConfigurationError

Bases: MemoryLayerError, RuntimeError

Raised when a repository cannot satisfy its declared configuration.

Source code in src/powercontext/builtin/artifacts/memory/errors.py
84
85
class MemoryBackendConfigurationError(MemoryLayerError, RuntimeError):
    """Raised when a repository cannot satisfy its declared configuration."""

MemoryCandidateRequest

Bases: BaseModel

Canonical evidence and bounded current entries offered to a pipeline.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
26
27
28
29
30
31
class MemoryCandidateRequest(BaseModel):
    """Canonical evidence and bounded current entries offered to a pipeline."""

    sources: tuple[Source, ...]
    artifacts: tuple[Artifact[object], ...]
    current_entries: tuple[MemoryEntryVersion, ...]

MemoryCapabilities

Bases: BaseModel

Backend features available for the configured deployment.

Source code in src/powercontext/builtin/artifacts/memory/models.py
30
31
32
33
34
35
36
class MemoryCapabilities(BaseModel):
    """Backend features available for the configured deployment."""

    fts: bool
    vector: bool = False
    hybrid: bool = False
    embedding_profile: EmbeddingProfile | None = None

MemoryChange

Bases: BaseModel

A compact entry change recorded by one Memory Revision.

Source code in src/powercontext/builtin/artifacts/memory/models.py
55
56
57
58
59
60
61
62
class MemoryChange(BaseModel):
    """A compact entry change recorded by one Memory Revision."""

    op: MemoryChangeOp
    entry_id: str
    from_entry_version_id: str | None
    to_entry_version_id: str | None
    reason: str | None = None

MemoryChannelHit

Bases: BaseModel

An identity-preserving candidate returned by one search channel.

Source code in src/powercontext/builtin/artifacts/memory/models.py
116
117
118
119
120
121
122
123
class MemoryChannelHit(BaseModel):
    """An identity-preserving candidate returned by one search channel."""

    memory_ref: ArtifactRef
    entry_id: str
    entry_version_id: str
    text: str
    distance: float | None = Field(default=None, ge=0.0, allow_inf_nan=False)

MemoryCitation

Bases: BaseModel

A stable Handoff anchor for one exact entry version.

Source code in src/powercontext/builtin/artifacts/memory/models.py
157
158
159
160
161
162
class MemoryCitation(BaseModel):
    """A stable Handoff anchor for one exact entry version."""

    memory_ref: ArtifactRef
    entry_id: str
    entry_version_id: str

MemoryCommit

Bases: BaseModel

A complete Memory Revision and every row changed atomically with it.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
43
44
45
46
47
48
49
50
class MemoryCommit(BaseModel):
    """A complete Memory Revision and every row changed atomically with it."""

    base: Memory | None
    memory: Memory
    content_hash: str
    entry_versions: tuple[MemoryEntryVersion, ...]
    projections: tuple[MemoryProjection, ...]

MemoryContent

Bases: BaseModel

The complete canonical content of one Memory Artifact Revision.

Source code in src/powercontext/builtin/artifacts/memory/models.py
65
66
67
68
69
70
71
72
73
class MemoryContent(BaseModel):
    """The complete canonical content of one Memory Artifact Revision."""

    manifest: MemoryManifest
    changes: tuple[MemoryChange, ...] = ()
    schema_version: Literal["powercontext.memory.v1"] = Field(
        default="powercontext.memory.v1",
        alias="schema",
    )

MemoryEntryInput

Bases: BaseModel

An untrusted proposed entry addition or content revision.

Source code in src/powercontext/builtin/artifacts/memory/models.py
82
83
84
85
86
87
88
89
90
class MemoryEntryInput(BaseModel):
    """An untrusted proposed entry addition or content revision."""

    kind: str
    text: str
    entry: MemoryEntryVersion | None = None
    sources: tuple[Source, ...] = ()
    artifacts: tuple[Artifact[object], ...] = ()
    reason: str | None = None

MemoryEntryVersion

Bases: BaseModel

One immutable version of a logical Memory entry.

Source code in src/powercontext/builtin/artifacts/memory/models.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
class MemoryEntryVersion(BaseModel):
    """One immutable version of a logical Memory entry."""

    memory_artifact_id: str
    entry_id: str
    entry_version_id: str
    version: int
    previous_version_id: str | None
    kind: str
    text: str
    entry_content_hash: str
    created_in_revision: int
    sources: tuple[SourceRef, ...] = ()
    artifacts: tuple[ArtifactRef, ...] = ()

MemoryEvidenceProjector

Bases: Protocol

Project adapter-owned evidence values into explicit JSON-visible content.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
64
65
66
67
68
69
70
71
72
73
74
75
class MemoryEvidenceProjector(Protocol):
    """Project adapter-owned evidence values into explicit JSON-visible content."""

    def project_source(self, source: Source, /) -> JsonValue:
        """Return the stable JSON projection exposed to the model."""

        ...

    def project_artifact(self, artifact: Artifact[object], /) -> JsonValue:
        """Return the stable JSON projection exposed to the model."""

        ...

project_artifact(artifact)

Return the stable JSON projection exposed to the model.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
72
73
74
75
def project_artifact(self, artifact: Artifact[object], /) -> JsonValue:
    """Return the stable JSON projection exposed to the model."""

    ...

project_source(source)

Return the stable JSON projection exposed to the model.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
67
68
69
70
def project_source(self, source: Source, /) -> JsonValue:
    """Return the stable JSON projection exposed to the model."""

    ...

MemoryExtractionCandidate

Bases: BaseModel

One model-proposed addition or revision with operation-local citations.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
47
48
49
50
51
52
53
54
55
class MemoryExtractionCandidate(BaseModel):
    """One model-proposed addition or revision with operation-local citations."""

    intent: MemoryExtractionIntent
    kind: KnownMemoryEntryKind
    text: str
    evidence_ids: tuple[str, ...]
    entry_id: str | None = None
    reason: str | None = None

MemoryExtractionCurrentEntry

Bases: BaseModel

One active entry from the selected Memory head.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
32
33
34
35
36
37
class MemoryExtractionCurrentEntry(BaseModel):
    """One active entry from the selected Memory head."""

    entry_id: str
    kind: str
    text: str

MemoryExtractionEvidence

Bases: BaseModel

One operation-local evidence value visible to the generator.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
24
25
26
27
28
29
class MemoryExtractionEvidence(BaseModel):
    """One operation-local evidence value visible to the generator."""

    evidence_id: str
    evidence_type: MemoryEvidenceType
    content: JsonValue

MemoryExtractionInput

Bases: BaseModel

Only bounded operation evidence and active current entries.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
40
41
42
43
44
class MemoryExtractionInput(BaseModel):
    """Only bounded operation evidence and active current entries."""

    evidence: tuple[MemoryExtractionEvidence, ...]
    current_entries: tuple[MemoryExtractionCurrentEntry, ...]

MemoryExtractionOutput

Bases: BaseModel

Schema-bound Memory extraction output; an empty tuple is a no-op.

Source code in src/powercontext/builtin/artifacts/memory/extraction.py
58
59
60
61
class MemoryExtractionOutput(BaseModel):
    """Schema-bound Memory extraction output; an empty tuple is a no-op."""

    candidates: tuple[MemoryExtractionCandidate, ...] = ()

MemoryExtractionProfile

Bases: StrEnum

Stable built-in policies for selecting Memory from bounded evidence.

Source code in src/powercontext/builtin/artifacts/memory/prompts.py
 6
 7
 8
 9
10
class MemoryExtractionProfile(StrEnum):
    """Stable built-in policies for selecting Memory from bounded evidence."""

    CODING = "coding"
    CONVERSATION = "conversation"

MemoryHit

Bases: BaseModel

A fused retrieval result anchored to exact Memory content.

Source code in src/powercontext/builtin/artifacts/memory/models.py
126
127
128
129
130
131
132
133
134
class MemoryHit(BaseModel):
    """A fused retrieval result anchored to exact Memory content."""

    memory_ref: ArtifactRef
    entry_id: str
    entry_version_id: str
    text: str
    score: float
    matched_by: tuple[MemoryMatchedBy, ...]

MemoryLayerError

Bases: PowerContextError

Base exception for Memory domain and repository failures.

Source code in src/powercontext/builtin/artifacts/memory/errors.py
6
7
class MemoryLayerError(PowerContextError):
    """Base exception for Memory domain and repository failures."""

MemoryManifest

Bases: BaseModel

The authoritative directory for one Memory Revision.

Source code in src/powercontext/builtin/artifacts/memory/models.py
48
49
50
51
52
class MemoryManifest(BaseModel):
    """The authoritative directory for one Memory Revision."""

    entries: tuple[MemoryManifestEntry, ...] = ()
    format: Literal["flat-v1"] = "flat-v1"

MemoryManifestEntry

Bases: BaseModel

One logical entry pointer and state in an immutable Revision.

Source code in src/powercontext/builtin/artifacts/memory/models.py
39
40
41
42
43
44
45
class MemoryManifestEntry(BaseModel):
    """One logical entry pointer and state in an immutable Revision."""

    entry_id: str
    entry_version_id: str
    entry_content_hash: str
    state: MemoryEntryState

MemoryProjection

Bases: BaseModel

A rebuildable active-head projection prepared outside a transaction.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
34
35
36
37
38
39
40
class MemoryProjection(BaseModel):
    """A rebuildable active-head projection prepared outside a transaction."""

    entry_version: MemoryEntryVersion
    searchable_text: str
    embedding: EmbeddingVector | None = None
    embedding_content_hash: str | None = None

MemoryRerankCandidate

Bases: _StrictModel

One identity-preserving coarse candidate exposed to the model.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
44
45
46
47
48
class MemoryRerankCandidate(_StrictModel):
    """One identity-preserving coarse candidate exposed to the model."""

    rank: int = Field(ge=1)
    text: str = Field(min_length=1)

MemoryRerankDecision dataclass

A validated sparse selection and its portable inference metadata.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
65
66
67
68
69
70
71
72
@dataclass(frozen=True, slots=True)
class MemoryRerankDecision:
    """A validated sparse selection and its portable inference metadata."""

    selected_ranks: tuple[int, ...]
    usage: InferenceUsage
    discarded_rank_count: int = 0
    used_fallback: bool = False

MemoryRerankInput

Bases: _StrictModel

One query and its bounded listwise candidate pool.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
51
52
53
54
55
56
class MemoryRerankInput(_StrictModel):
    """One query and its bounded listwise candidate pool."""

    query: str = Field(min_length=1)
    max_results: int = Field(ge=1)
    candidates: tuple[MemoryRerankCandidate, ...]

MemoryRerankMode

Bases: StrEnum

Deployment-selectable Memory reranking policies.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
33
34
35
36
37
class MemoryRerankMode(StrEnum):
    """Deployment-selectable Memory reranking policies."""

    NONE = "none"
    LLM = "llm"

MemoryRerankOutput

Bases: _StrictModel

Original coarse ranks selected in descending answer utility.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
59
60
61
62
class MemoryRerankOutput(_StrictModel):
    """Original coarse ranks selected in descending answer utility."""

    selected_ranks: tuple[int, ...]

MemoryRerankTrace

Bases: BaseModel

Observable listwise selection over one coarse retrieval pool.

Source code in src/powercontext/builtin/artifacts/memory/models.py
137
138
139
140
141
142
143
144
145
146
class MemoryRerankTrace(BaseModel):
    """Observable listwise selection over one coarse retrieval pool."""

    policy_id: str = Field(min_length=1)
    candidate_hits: tuple[MemoryHit, ...]
    selected_ranks: tuple[int, ...]
    discarded_rank_count: int = 0
    used_fallback: bool = False
    latency_ms: float = Field(ge=0.0, allow_inf_nan=False)
    usage: InferenceUsage

MemoryReranker

Bases: Protocol

Select final Memory hits from one already ordered coarse pool.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class MemoryReranker(Protocol):
    """Select final Memory hits from one already ordered coarse pool."""

    policy_id: str

    async def rerank(
        self,
        query: str,
        candidates: tuple[MemoryHit, ...],
        limit: int,
        /,
    ) -> MemoryRerankDecision:
        """Return validated original ranks without changing hit identity."""

        ...

rerank(query, candidates, limit) async

Return validated original ranks without changing hit identity.

Source code in src/powercontext/builtin/artifacts/memory/reranking.py
80
81
82
83
84
85
86
87
88
89
async def rerank(
    self,
    query: str,
    candidates: tuple[MemoryHit, ...],
    limit: int,
    /,
) -> MemoryRerankDecision:
    """Return validated original ranks without changing hit identity."""

    ...

MemoryRevisionChanges

Bases: BaseModel

The compact changes stored by one exact Memory Revision.

Source code in src/powercontext/builtin/artifacts/memory/models.py
109
110
111
112
113
class MemoryRevisionChanges(BaseModel):
    """The compact changes stored by one exact Memory Revision."""

    memory_ref: ArtifactRef
    changes: tuple[MemoryChange, ...]

MemorySearchChannels

Bases: BaseModel

Backend-internal channel rankings before shared fusion.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
72
73
74
75
76
class MemorySearchChannels(BaseModel):
    """Backend-internal channel rankings before shared fusion."""

    fts: tuple[MemoryChannelHit, ...] = ()
    vector: tuple[MemoryChannelHit, ...] = ()

MemorySearchRequest

Bases: BaseModel

A fully validated backend search request for explicit current heads.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
60
61
62
63
64
65
66
67
68
69
class MemorySearchRequest(BaseModel):
    """A fully validated backend search request for explicit current heads."""

    query: str
    analyzed_query: str
    memories: tuple[ArtifactRef, ...]
    candidate_limit: int
    mode: MemoryUsedSearchMode
    query_vector: EmbeddingVector | None = None
    embedding_profile: EmbeddingProfile | None = None

MemorySearchResult

Bases: BaseModel

Search hits together with the mode actually executed.

Source code in src/powercontext/builtin/artifacts/memory/models.py
149
150
151
152
153
154
class MemorySearchResult(BaseModel):
    """Search hits together with the mode actually executed."""

    mode: MemoryUsedSearchMode
    hits: tuple[MemoryHit, ...] = ()
    rerank: MemoryRerankTrace | None = None

MemoryService

Validate and orchestrate Memory operations without exposing storage details.

Source code in src/powercontext/builtin/artifacts/memory/service.py
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
class MemoryService:
    """Validate and orchestrate Memory operations without exposing storage details."""

    def __init__(
        self,
        *,
        backend: MemoryBackend,
        candidate_pipeline: CandidatePipeline | None = None,
        embedding_model: EmbeddingModel | None = None,
        reranker: MemoryReranker | None = None,
        rerank_candidate_limit: int = 30,
        source_resolver: _SourceResolver | None = None,
        artifact_resolver: _ArtifactResolver | None = None,
        id_factory: IdFactory | None = None,
    ) -> None:
        self._backend = backend
        self._candidate_pipeline = candidate_pipeline
        self._embedding_model = embedding_model
        if rerank_candidate_limit < 1:
            raise _InvalidMemoryOperationError("search-limit")
        self._reranker = reranker
        self._rerank_candidate_limit = rerank_candidate_limit
        self._source_resolver = source_resolver
        self._artifact_resolver = artifact_resolver
        self._id_factory = _default_id if id_factory is None else id_factory

    async def get(self, memory: Memory, /) -> Memory:
        """Return the canonical exact Memory Revision matching ``memory``."""

        return await self._canonical_memory(memory)

    async def latest(self, memory: Memory, /) -> Memory:
        """Return the current head of the same Memory identity."""

        canonical = await self.get(memory)
        return await self._backend.latest(canonical.artifact_id)

    async def revisions(self, memory: Memory, /) -> tuple[Memory, ...]:
        """Return the visible Memory history in ascending Revision order."""

        canonical = await self.get(memory)
        latest = await self._backend.latest(canonical.artifact_id)
        history = []
        for revision in range(1, latest.revision + 1):
            history.append(
                await self._backend.get(
                    ArtifactRef(family=Memory.family, artifact_id=canonical.artifact_id, revision=revision)
                )
            )
        return tuple(history)

    async def head(self, artifact_id: str, /) -> Memory:
        """Return the current Memory head by its stable Artifact identity."""

        return await self._backend.latest(artifact_id)

    async def revision(self, memory: ArtifactRef, /) -> Memory:
        """Return one exact Memory Revision by its stable reference."""

        return await self._backend.get(memory)

    async def remember(
        self,
        *,
        memory: Memory | None,
        sources: Sequence[Source] = (),
        artifacts: Sequence[Artifact[object]] = (),
        entries: Sequence[MemoryEntryInput] = (),
        mode: MemoryRememberMode = "auto",
    ) -> Memory | None:
        """Append or extract validated entry changes against one exact head."""

        return await self.apply(
            await self.plan_remember(
                memory=memory,
                sources=sources,
                artifacts=artifacts,
                entries=entries,
                mode=mode,
            )
        )

    async def plan_remember(
        self,
        *,
        memory: Memory | None,
        sources: Sequence[Source] = (),
        artifacts: Sequence[Artifact[object]] = (),
        entries: Sequence[MemoryEntryInput] = (),
        mode: MemoryRememberMode = "auto",
    ) -> MemoryWritePlan:
        """Validate and prepare a write without mutating authoritative storage."""

        selected_mode = _select_remember_mode(
            mode,
            has_entries=bool(entries),
            has_evidence=bool(sources or artifacts),
        )
        base = await self._canonical_base(memory)
        evidence = await self._canonical_operation_evidence(sources, artifacts)
        current_entries = () if base is None else await self._validated_entries(base)
        candidates = await self._candidates(
            selected_mode,
            tuple(entries),
            evidence,
            current_entries,
            active_version_ids=(
                frozenset()
                if base is None
                else frozenset(
                    item.entry_version_id for item in base.content.manifest.entries if item.state == "active"
                )
            ),
        )
        if not candidates:
            return MemoryWritePlan(result=base, commit=None)

        commit = await self._prepare_commit(
            base=base,
            candidates=candidates,
            evidence=evidence,
            current_entries=current_entries,
        )
        if commit is None:
            return MemoryWritePlan(result=base, commit=None)
        return MemoryWritePlan(result=commit.memory, commit=commit)

    async def apply(self, plan: MemoryWritePlan, /) -> Memory | None:
        """Apply one prepared write through this service's transaction boundary."""

        if plan.commit is None:
            return plan.result
        async with self._backend.begin() as unit_of_work:
            committed = await unit_of_work.commit(plan.commit)
        if plan.result != committed:
            raise InvalidMemoryCitationError("memory-mismatch")
        return committed

    async def forget(
        self,
        memory: Memory,
        *,
        entries: Sequence[MemoryEntryVersion],
        reason: str | None = None,
    ) -> Memory:
        """Deactivate logical entries without deleting immutable content."""

        return await self._set_entry_state(
            memory,
            entries=entries,
            target_state="inactive",
            reason=reason,
        )

    async def reactivate(
        self,
        memory: Memory,
        *,
        entries: Sequence[MemoryEntryVersion],
        reason: str | None = None,
    ) -> Memory:
        """Restore inactive logical entries without creating body versions."""

        return await self._set_entry_state(
            memory,
            entries=entries,
            target_state="active",
            reason=reason,
        )

    async def organize(
        self,
        memory: Memory,
        *,
        mode: Literal["default", "dedupe", "normalize"] = "default",
    ) -> Memory:
        """Apply only exact deduplication and canonical normalization."""

        if mode not in {"default", "dedupe", "normalize"}:
            raise _InvalidMemoryOperationError("organize-mode")
        base = await self._canonical_base(memory)
        current_entries = await self._validated_entries(base)
        manifest = {entry.entry_id: entry for entry in base.content.manifest.entries}
        current_by_entry = {entry.entry_id: entry for entry in current_entries}
        changes: list[MemoryChange] = []
        changed_ids: set[str] = set()
        new_versions: list[MemoryEntryVersion] = []

        if mode in {"default", "dedupe"}:
            dedupe_changes, changed_ids = self._deduplicate_manifest(manifest, current_by_entry)
            changes.extend(dedupe_changes)

        if mode in {"default", "normalize"}:
            normalize_changes, new_versions = self._normalize_manifest_entries(
                base,
                manifest,
                current_by_entry,
                skip=changed_ids,
            )
            changes.extend(normalize_changes)

        if not changes:
            return base
        return await self._commit_existing_transition(
            base=base,
            manifest=manifest,
            changes=changes,
            current_by_entry=current_by_entry,
            entry_versions=tuple(new_versions),
        )

    async def changes(
        self,
        memory: Memory,
        *,
        since_revision: int | None = None,
    ) -> tuple[MemoryRevisionChanges, ...]:
        """Read compact change summaries without expanding entry bodies."""

        target = await self._canonical_memory(memory)
        if since_revision is not None:
            if since_revision < 0:
                raise _InvalidMemoryOperationError("since-negative")
            if since_revision > target.revision:
                raise _InvalidMemoryOperationError("since-greater")
            if since_revision == target.revision:
                return ()
            if since_revision > 0:
                await self._backend.get(
                    ArtifactRef(
                        family=Memory.family,
                        artifact_id=target.artifact_id,
                        revision=since_revision,
                    )
                )
        return await self._backend.changes(target.as_ref(), since_revision)

    async def search(
        self,
        query: str,
        *,
        memories: Sequence[Memory],
        limit: int = 10,
        mode: MemorySearchMode = "auto",
    ) -> MemorySearchResult:
        """Search explicit current Memory heads with capability-safe fallback."""

        if not memories:
            raise _InvalidMemoryOperationError("search-memories")
        if limit < 1:
            raise _InvalidMemoryOperationError("search-limit")
        if mode not in {"fts", "vector", "hybrid", "auto"}:
            raise _InvalidMemoryOperationError("search-mode")
        selected_by_ref: dict[tuple[str, str, int], Memory] = {}
        for memory in memories:
            ref = memory.as_ref()
            selected_by_ref.setdefault((ref.family, ref.artifact_id, ref.revision), memory)
        selected = tuple(selected_by_ref.values())
        selected_memories = tuple(memory.as_ref() for memory in selected)
        await self._validate_search_heads(selected)
        capabilities = await self._backend.capabilities()
        selected_mode = await self._select_search_mode(
            mode,
            memories=selected_memories,
            capabilities=capabilities,
        )
        normalized_query = normalize_text(query)
        query_vector = None
        profile = None
        if selected_mode in {"vector", "hybrid"}:
            profile = capabilities.embedding_profile
            if profile is None:
                raise CapabilityNotSupportedError(selected_mode)
            if profile.distance != "l2" or profile.normalization != "unit":
                raise CapabilityNotSupportedError(
                    selected_mode,
                    "cosine admission requires a unit-normalized L2 embedding profile",
                )
            try:
                query_vector = (await self._embed_texts((normalized_query,), profile))[0]
            except (InferenceUnavailableError, InferenceTimeoutError) as error:
                if mode == "auto" and capabilities.fts:
                    selected_mode = "fts"
                    profile = None
                else:
                    raise CapabilityNotSupportedError(
                        selected_mode,
                        "embedding model is temporarily unavailable",
                    ) from error
        coarse_limit = limit if self._reranker is None else max(limit, self._rerank_candidate_limit)
        request = MemorySearchRequest(
            query=normalized_query,
            analyzed_query=analyze_text(normalized_query),
            memories=selected_memories,
            candidate_limit=max(coarse_limit * 4, 32),
            mode=selected_mode,
            query_vector=query_vector,
            embedding_profile=profile,
        )
        channels = await self._backend.search(request)
        admitted_fts = admit_fts_candidates(normalized_query, channels.fts)
        admitted_vector = admit_vector_candidates(channels.vector)
        hits = fuse_rankings(
            fts=admitted_fts if selected_mode in {"fts", "hybrid"} else (),
            vector=admitted_vector if selected_mode in {"vector", "hybrid"} else (),
            limit=coarse_limit,
        )
        return await self._reranked_search_result(
            mode=selected_mode,
            query=normalized_query,
            hits=hits,
            limit=limit,
        )

    async def _reranked_search_result(
        self,
        *,
        mode: MemoryUsedSearchMode,
        query: str,
        hits: tuple[MemoryHit, ...],
        limit: int,
    ) -> MemorySearchResult:
        if self._reranker is None or not hits:
            return MemorySearchResult(mode=mode, hits=hits[:limit])
        rerank_started = perf_counter()
        decision = await self._reranker.rerank(
            query,
            hits,
            min(limit, len(hits)),
        )
        rerank_latency_ms = (perf_counter() - rerank_started) * 1_000
        selected = tuple(hits[rank - 1] for rank in decision.selected_ranks)
        return MemorySearchResult(
            mode=mode,
            hits=selected,
            rerank=MemoryRerankTrace(
                policy_id=self._reranker.policy_id,
                candidate_hits=hits,
                selected_ranks=decision.selected_ranks,
                discarded_rank_count=decision.discarded_rank_count,
                used_fallback=decision.used_fallback,
                latency_ms=rerank_latency_ms,
                usage=decision.usage,
            ),
        )

    async def expand(
        self,
        hits: Sequence[MemoryHit],
        *,
        layer: Literal["full"] = "full",
    ) -> tuple[MemoryEntryVersion, ...]:
        """Expand exact hit anchors and reject cross-Revision substitutions."""

        if layer != "full":
            raise CapabilityNotSupportedError("expand-layer")
        hit_tuple = tuple(hits)
        versions = await self._backend.expand(hit_tuple)
        if len(versions) != len(hit_tuple):
            raise InvalidMemoryCitationError("expand-count")
        for hit, version in zip(hit_tuple, versions, strict=True):
            await self._validate_anchor(
                memory_ref=hit.memory_ref,
                entry_id=hit.entry_id,
                entry_version_id=hit.entry_version_id,
                version=version,
            )
        return versions

    async def entries(self, memory: Memory, /) -> tuple[MemoryEntryVersion, ...]:
        """Return the entry objects referenced by one exact current Memory head."""

        canonical = await self._canonical_memory(memory)
        return await self._validated_entries(canonical)

    async def rebuild_projections(self, embedding_model: EmbeddingModel | None = None, /) -> None:
        """Rebuild current-head search projections from authoritative Memory revisions."""

        await self._backend.rebuild_projections(embedding_model)

    async def validate_citation(self, citation: MemoryCitation) -> MemoryEntryVersion:
        """Resolve one exact Handoff citation."""

        hit = MemoryHit(
            memory_ref=citation.memory_ref,
            entry_id=citation.entry_id,
            entry_version_id=citation.entry_version_id,
            text="",
            score=0.0,
            matched_by=(),
        )
        return (await self.expand((hit,)))[0]

    async def _validate_search_heads(self, memories: tuple[Memory, ...]) -> None:
        for memory in memories:
            exact = await self._backend.get(memory.as_ref())
            latest = await self._backend.latest(memory.artifact_id)
            if exact != memory or exact.as_ref() != latest.as_ref():
                raise CapabilityNotSupportedError("head")

    async def _select_search_mode(
        self,
        requested: MemorySearchMode,
        *,
        memories: tuple[ArtifactRef, ...],
        capabilities: MemoryCapabilities,
    ) -> MemoryUsedSearchMode:
        if requested == "fts":
            if not capabilities.fts:
                raise CapabilityNotSupportedError("fts")
            return "fts"

        embedding_profile = None if self._embedding_model is None else self._embedding_model.profile
        profile = capabilities.embedding_profile
        profile_matches = profile is not None and embedding_profile == profile
        vector_complete = (
            await self._backend.vector_complete(memories, profile)
            if capabilities.vector and profile_matches and profile is not None
            else False
        )
        vector_available = capabilities.vector and profile_matches and vector_complete
        hybrid_available = capabilities.hybrid and vector_available

        if requested == "vector":
            if not vector_available:
                raise CapabilityNotSupportedError("vector")
            return "vector"
        if requested == "hybrid":
            if not hybrid_available:
                raise CapabilityNotSupportedError("hybrid")
            return "hybrid"
        if requested == "auto":
            if hybrid_available:
                return "hybrid"
            if capabilities.fts:
                return "fts"
            raise CapabilityNotSupportedError("fts")
        raise _InvalidMemoryOperationError("search-mode")

    def _deduplicate_manifest(
        self,
        manifest: dict[str, MemoryManifestEntry],
        current_by_entry: dict[str, MemoryEntryVersion],
    ) -> tuple[list[MemoryChange], set[str]]:
        groups: dict[bytes, list[str]] = {}
        for item in manifest.values():
            if item.state == "active":
                material = self._material_from_version(current_by_entry[item.entry_id])
                groups.setdefault(material.content_bytes, []).append(item.entry_id)

        changes: list[MemoryChange] = []
        changed_ids: set[str] = set()
        for entry_ids in groups.values():
            ordered = sorted(entry_ids, key=str.encode)
            for entry_id in ordered[1:]:
                item = manifest[entry_id]
                manifest[entry_id] = item.model_copy(update={"state": "inactive"})
                changes.append(
                    MemoryChange(
                        op="deactivate",
                        entry_id=entry_id,
                        from_entry_version_id=item.entry_version_id,
                        to_entry_version_id=None,
                        reason="dedupe",
                    )
                )
                changed_ids.add(entry_id)
        return changes, changed_ids

    def _normalize_manifest_entries(
        self,
        base: Memory,
        manifest: dict[str, MemoryManifestEntry],
        current_by_entry: dict[str, MemoryEntryVersion],
        *,
        skip: set[str],
    ) -> tuple[list[MemoryChange], list[MemoryEntryVersion]]:
        changes: list[MemoryChange] = []
        new_versions: list[MemoryEntryVersion] = []
        for entry_id, previous in tuple(current_by_entry.items()):
            if entry_id in skip:
                continue
            material = self._material_from_version(previous)
            if _is_canonical_version(previous, material):
                continue
            version = self._new_entry_version(
                memory_id=base.artifact_id,
                entry_id=entry_id,
                previous=previous,
                material=material,
                created_in_revision=base.revision + 1,
            )
            item = manifest[entry_id]
            manifest[entry_id] = _manifest_entry(version, state=item.state)
            current_by_entry[entry_id] = version
            new_versions.append(version)
            changes.append(
                MemoryChange(
                    op="revise",
                    entry_id=entry_id,
                    from_entry_version_id=previous.entry_version_id,
                    to_entry_version_id=version.entry_version_id,
                    reason="normalize",
                )
            )
        return changes, new_versions

    async def _set_entry_state(
        self,
        memory: Memory,
        *,
        entries: Sequence[MemoryEntryVersion],
        target_state: Literal["active", "inactive"],
        reason: str | None,
    ) -> Memory:
        base = await self._canonical_base(memory)
        current_entries = await self._validated_entries(base)
        manifest = {entry.entry_id: entry for entry in base.content.manifest.entries}
        current_by_entry = {entry.entry_id: entry for entry in current_entries}
        normalized_reason = normalize_reason(reason)
        changes: list[MemoryChange] = []

        seen: set[tuple[str, str]] = set()
        for entry in entries:
            identity = (entry.entry_id, entry.entry_version_id)
            if identity in seen:
                continue
            seen.add(identity)
            entry_id = validate_identifier(entry.entry_id)
            item = manifest.get(entry_id)
            if item is None:
                raise MemoryEntryNotFoundError(entry_id)
            current = current_by_entry.get(entry_id)
            if current is None:
                raise MemoryEntryNotFoundError(entry_id)
            if entry != current:
                raise InvalidMemoryCitationError("entry-mismatch")
            if item.state == target_state:
                continue
            manifest[entry_id] = item.model_copy(update={"state": target_state})
            changes.append(
                MemoryChange(
                    op="reactivate" if target_state == "active" else "deactivate",
                    entry_id=entry_id,
                    from_entry_version_id=None if target_state == "active" else item.entry_version_id,
                    to_entry_version_id=item.entry_version_id if target_state == "active" else None,
                    reason=normalized_reason,
                )
            )

        if not changes:
            return base
        return await self._commit_existing_transition(
            base=base,
            manifest=manifest,
            changes=changes,
            current_by_entry=current_by_entry,
            entry_versions=(),
        )

    async def _commit_existing_transition(
        self,
        *,
        base: Memory,
        manifest: dict[str, MemoryManifestEntry],
        changes: Sequence[MemoryChange],
        current_by_entry: dict[str, MemoryEntryVersion],
        entry_versions: tuple[MemoryEntryVersion, ...],
    ) -> Memory:
        sorted_manifest = tuple(sorted(manifest.values(), key=lambda item: item.entry_id.encode("utf-8")))
        sorted_changes = tuple(sorted(changes, key=lambda change: change.entry_id.encode("utf-8")))
        content = MemoryContent(manifest=MemoryManifest(entries=sorted_manifest), changes=sorted_changes)
        memory = Memory(
            artifact_id=base.artifact_id,
            revision=base.revision + 1,
            content=content,
            lineage=ArtifactLineage(),
        )
        projections = await self._prepare_projections(
            base=base,
            manifest_entries=sorted_manifest,
            versions_by_entry=current_by_entry,
            changed_version_ids=frozenset(version.entry_version_id for version in entry_versions),
        )
        commit = MemoryCommit(
            base=base,
            memory=memory,
            content_hash=memory_content_hash(content),
            entry_versions=entry_versions,
            projections=projections,
        )
        async with self._backend.begin() as unit_of_work:
            return await unit_of_work.commit(commit)

    async def _validate_anchor(
        self,
        *,
        memory_ref: ArtifactRef,
        entry_id: str,
        entry_version_id: str,
        version: MemoryEntryVersion,
    ) -> None:
        memory = await self._backend.get(memory_ref)
        item = next(
            (candidate for candidate in memory.content.manifest.entries if candidate.entry_id == entry_id),
            None,
        )
        if (
            item is None
            or item.entry_version_id != entry_version_id
            or version.memory_artifact_id != memory_ref.artifact_id
            or version.entry_id != entry_id
            or version.entry_version_id != entry_version_id
        ):
            raise InvalidMemoryCitationError("expand-anchor")
        material = self._material_from_version(version)
        if material.content_hash != item.entry_content_hash or version.entry_content_hash != item.entry_content_hash:
            raise InvalidMemoryCitationError("hash-mismatch")

    async def _prepare_projections(
        self,
        *,
        base: Memory | None,
        manifest_entries: tuple[MemoryManifestEntry, ...],
        versions_by_entry: dict[str, MemoryEntryVersion],
        changed_version_ids: frozenset[str],
    ) -> tuple[MemoryProjection, ...]:
        """Build active-head projections, reusing unchanged vectors and embedding only changes."""

        previous = await self._previous_projections(base)
        prepared: list[MemoryProjection] = []
        embed_indices: list[int] = []
        for item in manifest_entries:
            if item.state != "active":
                continue
            version = versions_by_entry[item.entry_id]
            if version.entry_version_id != item.entry_version_id:
                raise InvalidMemoryCitationError("projection-version")
            searchable_text = analyze_text(version.text)
            reused = self._reused_projection(previous.get(version.entry_version_id), version, searchable_text)
            if reused is not None and version.entry_version_id not in changed_version_ids:
                prepared.append(reused)
                continue
            prepared.append(MemoryProjection(entry_version=version, searchable_text=searchable_text))
            embed_indices.append(len(prepared) - 1)
        return await self._attach_embeddings(tuple(prepared), embed_indices)

    async def _previous_projections(self, base: Memory | None) -> dict[str, MemoryProjection]:
        if base is None:
            return {}
        return {
            projection.entry_version.entry_version_id: projection
            for projection in await self._backend.projections(base.as_ref())
        }

    def _reused_projection(
        self,
        previous: MemoryProjection | None,
        version: MemoryEntryVersion,
        searchable_text: str,
    ) -> MemoryProjection | None:
        if previous is None:
            return None
        if (
            previous.entry_version.entry_version_id != version.entry_version_id
            or previous.entry_version.entry_content_hash != version.entry_content_hash
            or previous.searchable_text != searchable_text
        ):
            return None
        return previous.model_copy(
            update={
                "entry_version": version,
                "searchable_text": searchable_text,
            }
        )

    async def _attach_embeddings(
        self,
        projections: tuple[MemoryProjection, ...],
        embed_indices: Sequence[int],
    ) -> tuple[MemoryProjection, ...]:
        if not projections or not embed_indices or self._embedding_model is None:
            return projections
        capabilities = await self._backend.capabilities()
        profile = capabilities.embedding_profile
        if not capabilities.vector or profile is None:
            return projections
        embedding_profile = self._embedding_model.profile
        if embedding_profile != profile:
            return projections
        try:
            vectors = await self._embed_texts(
                tuple(projections[index].entry_version.text for index in embed_indices),
                profile,
            )
        except (InferenceUnavailableError, InferenceTimeoutError):
            return projections
        updated = list(projections)
        for index, vector in zip(embed_indices, vectors, strict=True):
            projection = updated[index]
            updated[index] = projection.model_copy(
                update={
                    "embedding": vector,
                    "embedding_content_hash": embedding_content_hash(
                        profile_id=profile.profile_id,
                        model=profile.model,
                        dimension=profile.dimension,
                        distance=profile.distance,
                        normalization=profile.normalization,
                        entry_content_hash=projection.entry_version.entry_content_hash,
                    ),
                }
            )
        return tuple(updated)

    async def _embed_texts(
        self,
        texts: tuple[str, ...],
        profile: EmbeddingProfile,
    ) -> tuple[EmbeddingVector, ...]:
        embedding_model = self._embedding_model
        if embedding_model is None or embedding_model.profile != profile:
            raise CapabilityNotSupportedError("embedding-profile")
        result = await embedding_model.embed(texts)
        vectors = result.vectors
        if len(vectors) != len(texts):
            raise InvalidEmbeddingError("count")
        return tuple(
            canonical_embedding(
                vector,
                dimension=profile.dimension,
                normalization=profile.normalization,
            )
            for vector in vectors
        )

    @overload
    async def _canonical_base(self, memory: None) -> None: ...

    @overload
    async def _canonical_base(self, memory: Memory) -> Memory: ...

    async def _canonical_base(self, memory: Memory | None) -> Memory | None:
        if memory is None:
            return None
        exact = await self._backend.get(memory.as_ref())
        if exact != memory:
            raise InvalidMemoryCitationError("base-mismatch")
        latest = await self._backend.latest(memory.artifact_id)
        if latest != exact:
            raise RevisionConflictError(memory, latest)
        return exact

    async def _canonical_memory(self, memory: Memory) -> Memory:
        exact = await self._backend.get(memory.as_ref())
        if exact != memory:
            raise InvalidMemoryCitationError("memory-mismatch")
        return exact

    async def _canonical_operation_evidence(
        self,
        sources: Sequence[Source],
        artifacts: Sequence[Artifact[object]],
    ) -> _OperationEvidence:
        canonical_sources: list[Source] = []
        for source in sources:
            if self._source_resolver is None:
                raise InvalidMemoryEvidenceError("source-resolver")
            _append_unique(canonical_sources, await self._source_resolver.get(source))

        canonical_artifacts: list[Artifact[object]] = []
        for artifact in artifacts:
            if self._artifact_resolver is None:
                raise InvalidMemoryEvidenceError("artifact-resolver")
            _append_unique(canonical_artifacts, await self._artifact_resolver.get(artifact))
        return _OperationEvidence(
            sources=tuple(canonical_sources),
            artifacts=tuple(canonical_artifacts),
        )

    async def _validated_entries(self, memory: Memory) -> tuple[MemoryEntryVersion, ...]:
        versions = await self._backend.entries(memory.as_ref())
        versions_by_id = {version.entry_version_id: version for version in versions}
        if len(versions_by_id) != len(versions):
            raise InvalidMemoryCitationError("duplicate-versions")

        ordered: list[MemoryEntryVersion] = []
        for item in memory.content.manifest.entries:
            version = versions_by_id.get(item.entry_version_id)
            if version is None:
                raise InvalidMemoryCitationError("missing-version")
            if version.memory_artifact_id != memory.artifact_id or version.entry_id != item.entry_id:
                raise InvalidMemoryCitationError("cross-identity")
            material = self._material_from_version(version)
            if material.content_hash != item.entry_content_hash:
                raise InvalidMemoryCitationError("hash-mismatch")
            ordered.append(version)
        return tuple(ordered)

    async def _candidates(
        self,
        mode: Literal["append", "extract"],
        entries: tuple[MemoryEntryInput, ...],
        evidence: _OperationEvidence,
        current_entries: tuple[MemoryEntryVersion, ...],
        *,
        active_version_ids: frozenset[str],
    ) -> tuple[MemoryEntryInput, ...]:
        if mode == "append":
            return entries
        if self._candidate_pipeline is None:
            raise CapabilityNotSupportedError("extract")
        bounded = tuple(entry for entry in current_entries if entry.entry_version_id in active_version_ids)
        return await self._candidate_pipeline.extract(
            MemoryCandidateRequest(
                sources=evidence.sources,
                artifacts=evidence.artifacts,
                current_entries=bounded,
            )
        )

    async def _prepare_commit(
        self,
        *,
        base: Memory | None,
        candidates: tuple[MemoryEntryInput, ...],
        evidence: _OperationEvidence,
        current_entries: tuple[MemoryEntryVersion, ...],
    ) -> MemoryCommit | None:
        memory_id = base.artifact_id if base is not None else self._new_id("memory")
        next_revision = 1 if base is None else base.revision + 1
        manifest = {} if base is None else {entry.entry_id: entry for entry in base.content.manifest.entries}
        current_by_entry = {entry.entry_id: entry for entry in current_entries}
        new_versions: list[MemoryEntryVersion] = []
        changes: list[MemoryChange] = []
        targeted: set[str] = set()
        new_content: set[bytes] = {
            self._material_from_version(version).content_bytes
            for version in current_entries
            if manifest[version.entry_id].state == "active"
        }

        for candidate in candidates:
            if candidate.entry is None:
                material = await self._material_from_candidate(candidate, evidence.sources, evidence.artifacts)
                if material.content_bytes in new_content:
                    continue
                new_content.add(material.content_bytes)
                entry_id = self._new_id("entry")
                if entry_id in manifest:
                    raise _InvalidMemoryOperationError("id-collision")
                version = self._new_entry_version(
                    memory_id=memory_id,
                    entry_id=entry_id,
                    previous=None,
                    material=material,
                    created_in_revision=next_revision,
                )
                manifest[entry_id] = _manifest_entry(version, state="active")
                current_by_entry[entry_id] = version
                new_versions.append(version)
                changes.append(
                    MemoryChange(
                        op="add",
                        entry_id=entry_id,
                        from_entry_version_id=None,
                        to_entry_version_id=version.entry_version_id,
                        reason=normalize_reason(candidate.reason),
                    )
                )
                continue

            entry_id, previous = self._claim_revision_target(candidate, current_by_entry, targeted)
            item = manifest.get(entry_id)
            if item is None:
                raise MemoryEntryNotFoundError(entry_id)
            if item.state == "inactive":
                raise MemoryEntryInactiveError(entry_id)
            material = await self._material_from_candidate(
                candidate,
                evidence.sources,
                evidence.artifacts,
                previous=previous,
            )
            previous_material = self._material_from_version(previous)
            if (
                material.content_hash == previous_material.content_hash
                and material.content_bytes == previous_material.content_bytes
            ):
                continue
            version = self._new_entry_version(
                memory_id=memory_id,
                entry_id=entry_id,
                previous=previous,
                material=material,
                created_in_revision=next_revision,
            )
            manifest[entry_id] = _manifest_entry(version, state="active")
            current_by_entry[entry_id] = version
            new_versions.append(version)
            changes.append(
                MemoryChange(
                    op="revise",
                    entry_id=entry_id,
                    from_entry_version_id=previous.entry_version_id,
                    to_entry_version_id=version.entry_version_id,
                    reason=normalize_reason(candidate.reason),
                )
            )

        if not changes:
            return None

        sorted_manifest = tuple(sorted(manifest.values(), key=lambda item: item.entry_id.encode("utf-8")))
        sorted_changes = tuple(sorted(changes, key=lambda change: change.entry_id.encode("utf-8")))
        content = MemoryContent(manifest=MemoryManifest(entries=sorted_manifest), changes=sorted_changes)
        memory = Memory(
            artifact_id=memory_id,
            revision=next_revision,
            content=content,
            lineage=ArtifactLineage(
                sources=self._source_refs(evidence.sources),
                artifacts=tuple(artifact.as_ref() for artifact in evidence.artifacts),
            ),
        )
        projections = await self._prepare_projections(
            base=base,
            manifest_entries=sorted_manifest,
            versions_by_entry=current_by_entry,
            changed_version_ids=frozenset(version.entry_version_id for version in new_versions),
        )
        return MemoryCommit(
            base=base,
            memory=memory,
            content_hash=memory_content_hash(content),
            entry_versions=tuple(new_versions),
            projections=projections,
        )

    @staticmethod
    def _claim_revision_target(
        candidate: MemoryEntryInput,
        current_by_entry: dict[str, MemoryEntryVersion],
        targeted: set[str],
    ) -> tuple[str, MemoryEntryVersion]:
        entry = candidate.entry
        if entry is None:
            raise InvalidMemoryCitationError("entry-missing")
        entry_id = validate_identifier(entry.entry_id)
        if entry_id in targeted:
            raise _InvalidMemoryOperationError("duplicate-target")
        targeted.add(entry_id)
        previous = current_by_entry.get(entry_id)
        if previous is None:
            raise MemoryEntryNotFoundError(entry_id)
        if entry != previous:
            raise InvalidMemoryCitationError("entry-mismatch")
        return entry_id, previous

    async def _material_from_candidate(
        self,
        candidate: MemoryEntryInput,
        allowed_sources: Sequence[Source],
        allowed_artifacts: Sequence[Artifact[object]],
        *,
        previous: MemoryEntryVersion | None = None,
    ) -> _EntryMaterial:
        previous_artifacts = () if previous is None else previous.artifacts
        candidate_sources = await self._canonical_candidate_sources(candidate.sources, allowed_sources)
        candidate_source_refs = self._source_refs(candidate_sources)
        candidate_artifacts = await self._canonical_candidate_artifacts(
            candidate.artifacts,
            allowed_artifacts,
            previous_artifacts,
        )
        if previous is None:
            sources = candidate_source_refs
            artifacts = candidate_artifacts
        else:
            # Revises retain predecessor evidence and add current candidate evidence.
            sources = (*previous.sources, *candidate_source_refs)
            artifacts = (*previous.artifacts, *candidate_artifacts)
        try:
            return self._entry_material(
                kind=candidate.kind,
                text=candidate.text,
                sources=sources,
                artifacts=artifacts,
            )
        except (TypeError, ValueError) as error:
            raise InvalidMemoryCandidateError("canonical", str(error)) from error

    async def _canonical_candidate_sources(
        self,
        values: Sequence[Source],
        allowed: Sequence[Source],
    ) -> tuple[Source, ...]:
        result: list[Source] = []
        for value in values:
            canonical = value if self._source_resolver is None else await self._source_resolver.get(value)
            match = _equal_member(canonical, allowed)
            if match is None:
                raise InvalidMemoryEvidenceError("source-outside")
            _append_unique(result, match)
        return tuple(result)

    async def _canonical_candidate_artifacts(
        self,
        values: Sequence[Artifact[object]],
        allowed: Sequence[Artifact[object]],
        previous: Sequence[ArtifactRef],
    ) -> tuple[ArtifactRef, ...]:
        result: list[ArtifactRef] = []
        allowed_refs = tuple(artifact.as_ref() for artifact in allowed)
        for value in values:
            canonical = value if self._artifact_resolver is None else await self._artifact_resolver.get(value)
            reference = canonical.as_ref()
            if reference not in (*allowed_refs, *previous):
                raise InvalidMemoryEvidenceError("artifact-outside")
            if reference not in result:
                result.append(reference)
        return tuple(result)

    def _material_from_version(self, version: MemoryEntryVersion) -> _EntryMaterial:
        return self._entry_material(
            kind=version.kind,
            text=version.text,
            sources=version.sources,
            artifacts=version.artifacts,
        )

    def _entry_material(
        self,
        *,
        kind: str,
        text: str,
        sources: Sequence[SourceRef],
        artifacts: Sequence[ArtifactRef],
    ) -> _EntryMaterial:
        normalized_kind = normalize_kind(kind)
        normalized_text = normalize_text(text)
        canonical_sources = _canonical_source_refs(sources)
        canonical_artifacts = _canonical_artifact_refs(artifacts)
        source_refs = tuple(source.model_dump(mode="json") for source in canonical_sources)
        artifact_refs = tuple(artifact.model_dump(mode="json") for artifact in canonical_artifacts)
        content_bytes = entry_content_bytes(
            kind=normalized_kind,
            text=normalized_text,
            source_refs=source_refs,
            artifact_refs=artifact_refs,
        )
        return _EntryMaterial(
            kind=normalized_kind,
            text=normalized_text,
            sources=canonical_sources,
            artifacts=canonical_artifacts,
            content_bytes=content_bytes,
            content_hash=entry_content_hash(
                kind=normalized_kind,
                text=normalized_text,
                source_refs=source_refs,
                artifact_refs=artifact_refs,
            ),
        )

    def _source_refs(self, sources: Sequence[Source]) -> tuple[SourceRef, ...]:
        if not sources:
            return ()
        if self._source_resolver is None:
            raise InvalidMemoryEvidenceError("source-adapter")
        keyed: dict[bytes, SourceRef] = {}
        for source in sources:
            try:
                reference = self._source_resolver.as_ref(source)
            except (KeyError, LookupError, TypeError, ValueError):
                raise InvalidMemoryEvidenceError("source-adapter") from None
            keyed.setdefault(canonical_json(reference.model_dump(mode="json")), reference)
        return tuple(keyed[key] for key in sorted(keyed))

    def _new_entry_version(
        self,
        *,
        memory_id: str,
        entry_id: str,
        previous: MemoryEntryVersion | None,
        material: _EntryMaterial,
        created_in_revision: int,
    ) -> MemoryEntryVersion:
        return MemoryEntryVersion(
            memory_artifact_id=memory_id,
            entry_id=entry_id,
            entry_version_id=self._new_id("version"),
            version=1 if previous is None else previous.version + 1,
            previous_version_id=None if previous is None else previous.entry_version_id,
            kind=material.kind,
            text=material.text,
            sources=material.sources,
            artifacts=material.artifacts,
            entry_content_hash=material.content_hash,
            created_in_revision=created_in_revision,
        )

    def _new_id(self, kind: str) -> str:
        return validate_identifier(self._id_factory(kind))

apply(plan) async

Apply one prepared write through this service's transaction boundary.

Source code in src/powercontext/builtin/artifacts/memory/service.py
252
253
254
255
256
257
258
259
260
261
async def apply(self, plan: MemoryWritePlan, /) -> Memory | None:
    """Apply one prepared write through this service's transaction boundary."""

    if plan.commit is None:
        return plan.result
    async with self._backend.begin() as unit_of_work:
        committed = await unit_of_work.commit(plan.commit)
    if plan.result != committed:
        raise InvalidMemoryCitationError("memory-mismatch")
    return committed

changes(memory, *, since_revision=None) async

Read compact change summaries without expanding entry bodies.

Source code in src/powercontext/builtin/artifacts/memory/service.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
async def changes(
    self,
    memory: Memory,
    *,
    since_revision: int | None = None,
) -> tuple[MemoryRevisionChanges, ...]:
    """Read compact change summaries without expanding entry bodies."""

    target = await self._canonical_memory(memory)
    if since_revision is not None:
        if since_revision < 0:
            raise _InvalidMemoryOperationError("since-negative")
        if since_revision > target.revision:
            raise _InvalidMemoryOperationError("since-greater")
        if since_revision == target.revision:
            return ()
        if since_revision > 0:
            await self._backend.get(
                ArtifactRef(
                    family=Memory.family,
                    artifact_id=target.artifact_id,
                    revision=since_revision,
                )
            )
    return await self._backend.changes(target.as_ref(), since_revision)

entries(memory) async

Return the entry objects referenced by one exact current Memory head.

Source code in src/powercontext/builtin/artifacts/memory/service.py
494
495
496
497
498
async def entries(self, memory: Memory, /) -> tuple[MemoryEntryVersion, ...]:
    """Return the entry objects referenced by one exact current Memory head."""

    canonical = await self._canonical_memory(memory)
    return await self._validated_entries(canonical)

expand(hits, *, layer='full') async

Expand exact hit anchors and reject cross-Revision substitutions.

Source code in src/powercontext/builtin/artifacts/memory/service.py
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
async def expand(
    self,
    hits: Sequence[MemoryHit],
    *,
    layer: Literal["full"] = "full",
) -> tuple[MemoryEntryVersion, ...]:
    """Expand exact hit anchors and reject cross-Revision substitutions."""

    if layer != "full":
        raise CapabilityNotSupportedError("expand-layer")
    hit_tuple = tuple(hits)
    versions = await self._backend.expand(hit_tuple)
    if len(versions) != len(hit_tuple):
        raise InvalidMemoryCitationError("expand-count")
    for hit, version in zip(hit_tuple, versions, strict=True):
        await self._validate_anchor(
            memory_ref=hit.memory_ref,
            entry_id=hit.entry_id,
            entry_version_id=hit.entry_version_id,
            version=version,
        )
    return versions

forget(memory, *, entries, reason=None) async

Deactivate logical entries without deleting immutable content.

Source code in src/powercontext/builtin/artifacts/memory/service.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
async def forget(
    self,
    memory: Memory,
    *,
    entries: Sequence[MemoryEntryVersion],
    reason: str | None = None,
) -> Memory:
    """Deactivate logical entries without deleting immutable content."""

    return await self._set_entry_state(
        memory,
        entries=entries,
        target_state="inactive",
        reason=reason,
    )

get(memory) async

Return the canonical exact Memory Revision matching memory.

Source code in src/powercontext/builtin/artifacts/memory/service.py
151
152
153
154
async def get(self, memory: Memory, /) -> Memory:
    """Return the canonical exact Memory Revision matching ``memory``."""

    return await self._canonical_memory(memory)

head(artifact_id) async

Return the current Memory head by its stable Artifact identity.

Source code in src/powercontext/builtin/artifacts/memory/service.py
176
177
178
179
async def head(self, artifact_id: str, /) -> Memory:
    """Return the current Memory head by its stable Artifact identity."""

    return await self._backend.latest(artifact_id)

latest(memory) async

Return the current head of the same Memory identity.

Source code in src/powercontext/builtin/artifacts/memory/service.py
156
157
158
159
160
async def latest(self, memory: Memory, /) -> Memory:
    """Return the current head of the same Memory identity."""

    canonical = await self.get(memory)
    return await self._backend.latest(canonical.artifact_id)

organize(memory, *, mode='default') async

Apply only exact deduplication and canonical normalization.

Source code in src/powercontext/builtin/artifacts/memory/service.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
async def organize(
    self,
    memory: Memory,
    *,
    mode: Literal["default", "dedupe", "normalize"] = "default",
) -> Memory:
    """Apply only exact deduplication and canonical normalization."""

    if mode not in {"default", "dedupe", "normalize"}:
        raise _InvalidMemoryOperationError("organize-mode")
    base = await self._canonical_base(memory)
    current_entries = await self._validated_entries(base)
    manifest = {entry.entry_id: entry for entry in base.content.manifest.entries}
    current_by_entry = {entry.entry_id: entry for entry in current_entries}
    changes: list[MemoryChange] = []
    changed_ids: set[str] = set()
    new_versions: list[MemoryEntryVersion] = []

    if mode in {"default", "dedupe"}:
        dedupe_changes, changed_ids = self._deduplicate_manifest(manifest, current_by_entry)
        changes.extend(dedupe_changes)

    if mode in {"default", "normalize"}:
        normalize_changes, new_versions = self._normalize_manifest_entries(
            base,
            manifest,
            current_by_entry,
            skip=changed_ids,
        )
        changes.extend(normalize_changes)

    if not changes:
        return base
    return await self._commit_existing_transition(
        base=base,
        manifest=manifest,
        changes=changes,
        current_by_entry=current_by_entry,
        entry_versions=tuple(new_versions),
    )

plan_remember(*, memory, sources=(), artifacts=(), entries=(), mode='auto') async

Validate and prepare a write without mutating authoritative storage.

Source code in src/powercontext/builtin/artifacts/memory/service.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
async def plan_remember(
    self,
    *,
    memory: Memory | None,
    sources: Sequence[Source] = (),
    artifacts: Sequence[Artifact[object]] = (),
    entries: Sequence[MemoryEntryInput] = (),
    mode: MemoryRememberMode = "auto",
) -> MemoryWritePlan:
    """Validate and prepare a write without mutating authoritative storage."""

    selected_mode = _select_remember_mode(
        mode,
        has_entries=bool(entries),
        has_evidence=bool(sources or artifacts),
    )
    base = await self._canonical_base(memory)
    evidence = await self._canonical_operation_evidence(sources, artifacts)
    current_entries = () if base is None else await self._validated_entries(base)
    candidates = await self._candidates(
        selected_mode,
        tuple(entries),
        evidence,
        current_entries,
        active_version_ids=(
            frozenset()
            if base is None
            else frozenset(
                item.entry_version_id for item in base.content.manifest.entries if item.state == "active"
            )
        ),
    )
    if not candidates:
        return MemoryWritePlan(result=base, commit=None)

    commit = await self._prepare_commit(
        base=base,
        candidates=candidates,
        evidence=evidence,
        current_entries=current_entries,
    )
    if commit is None:
        return MemoryWritePlan(result=base, commit=None)
    return MemoryWritePlan(result=commit.memory, commit=commit)

reactivate(memory, *, entries, reason=None) async

Restore inactive logical entries without creating body versions.

Source code in src/powercontext/builtin/artifacts/memory/service.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
async def reactivate(
    self,
    memory: Memory,
    *,
    entries: Sequence[MemoryEntryVersion],
    reason: str | None = None,
) -> Memory:
    """Restore inactive logical entries without creating body versions."""

    return await self._set_entry_state(
        memory,
        entries=entries,
        target_state="active",
        reason=reason,
    )

rebuild_projections(embedding_model=None) async

Rebuild current-head search projections from authoritative Memory revisions.

Source code in src/powercontext/builtin/artifacts/memory/service.py
500
501
502
503
async def rebuild_projections(self, embedding_model: EmbeddingModel | None = None, /) -> None:
    """Rebuild current-head search projections from authoritative Memory revisions."""

    await self._backend.rebuild_projections(embedding_model)

remember(*, memory, sources=(), artifacts=(), entries=(), mode='auto') async

Append or extract validated entry changes against one exact head.

Source code in src/powercontext/builtin/artifacts/memory/service.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
async def remember(
    self,
    *,
    memory: Memory | None,
    sources: Sequence[Source] = (),
    artifacts: Sequence[Artifact[object]] = (),
    entries: Sequence[MemoryEntryInput] = (),
    mode: MemoryRememberMode = "auto",
) -> Memory | None:
    """Append or extract validated entry changes against one exact head."""

    return await self.apply(
        await self.plan_remember(
            memory=memory,
            sources=sources,
            artifacts=artifacts,
            entries=entries,
            mode=mode,
        )
    )

revision(memory) async

Return one exact Memory Revision by its stable reference.

Source code in src/powercontext/builtin/artifacts/memory/service.py
181
182
183
184
async def revision(self, memory: ArtifactRef, /) -> Memory:
    """Return one exact Memory Revision by its stable reference."""

    return await self._backend.get(memory)

revisions(memory) async

Return the visible Memory history in ascending Revision order.

Source code in src/powercontext/builtin/artifacts/memory/service.py
162
163
164
165
166
167
168
169
170
171
172
173
174
async def revisions(self, memory: Memory, /) -> tuple[Memory, ...]:
    """Return the visible Memory history in ascending Revision order."""

    canonical = await self.get(memory)
    latest = await self._backend.latest(canonical.artifact_id)
    history = []
    for revision in range(1, latest.revision + 1):
        history.append(
            await self._backend.get(
                ArtifactRef(family=Memory.family, artifact_id=canonical.artifact_id, revision=revision)
            )
        )
    return tuple(history)

search(query, *, memories, limit=10, mode='auto') async

Search explicit current Memory heads with capability-safe fallback.

Source code in src/powercontext/builtin/artifacts/memory/service.py
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
async def search(
    self,
    query: str,
    *,
    memories: Sequence[Memory],
    limit: int = 10,
    mode: MemorySearchMode = "auto",
) -> MemorySearchResult:
    """Search explicit current Memory heads with capability-safe fallback."""

    if not memories:
        raise _InvalidMemoryOperationError("search-memories")
    if limit < 1:
        raise _InvalidMemoryOperationError("search-limit")
    if mode not in {"fts", "vector", "hybrid", "auto"}:
        raise _InvalidMemoryOperationError("search-mode")
    selected_by_ref: dict[tuple[str, str, int], Memory] = {}
    for memory in memories:
        ref = memory.as_ref()
        selected_by_ref.setdefault((ref.family, ref.artifact_id, ref.revision), memory)
    selected = tuple(selected_by_ref.values())
    selected_memories = tuple(memory.as_ref() for memory in selected)
    await self._validate_search_heads(selected)
    capabilities = await self._backend.capabilities()
    selected_mode = await self._select_search_mode(
        mode,
        memories=selected_memories,
        capabilities=capabilities,
    )
    normalized_query = normalize_text(query)
    query_vector = None
    profile = None
    if selected_mode in {"vector", "hybrid"}:
        profile = capabilities.embedding_profile
        if profile is None:
            raise CapabilityNotSupportedError(selected_mode)
        if profile.distance != "l2" or profile.normalization != "unit":
            raise CapabilityNotSupportedError(
                selected_mode,
                "cosine admission requires a unit-normalized L2 embedding profile",
            )
        try:
            query_vector = (await self._embed_texts((normalized_query,), profile))[0]
        except (InferenceUnavailableError, InferenceTimeoutError) as error:
            if mode == "auto" and capabilities.fts:
                selected_mode = "fts"
                profile = None
            else:
                raise CapabilityNotSupportedError(
                    selected_mode,
                    "embedding model is temporarily unavailable",
                ) from error
    coarse_limit = limit if self._reranker is None else max(limit, self._rerank_candidate_limit)
    request = MemorySearchRequest(
        query=normalized_query,
        analyzed_query=analyze_text(normalized_query),
        memories=selected_memories,
        candidate_limit=max(coarse_limit * 4, 32),
        mode=selected_mode,
        query_vector=query_vector,
        embedding_profile=profile,
    )
    channels = await self._backend.search(request)
    admitted_fts = admit_fts_candidates(normalized_query, channels.fts)
    admitted_vector = admit_vector_candidates(channels.vector)
    hits = fuse_rankings(
        fts=admitted_fts if selected_mode in {"fts", "hybrid"} else (),
        vector=admitted_vector if selected_mode in {"vector", "hybrid"} else (),
        limit=coarse_limit,
    )
    return await self._reranked_search_result(
        mode=selected_mode,
        query=normalized_query,
        hits=hits,
        limit=limit,
    )

validate_citation(citation) async

Resolve one exact Handoff citation.

Source code in src/powercontext/builtin/artifacts/memory/service.py
505
506
507
508
509
510
511
512
513
514
515
516
async def validate_citation(self, citation: MemoryCitation) -> MemoryEntryVersion:
    """Resolve one exact Handoff citation."""

    hit = MemoryHit(
        memory_ref=citation.memory_ref,
        entry_id=citation.entry_id,
        entry_version_id=citation.entry_version_id,
        text="",
        score=0.0,
        matched_by=(),
    )
    return (await self.expand((hit,)))[0]

MemoryUnitOfWork

Bases: Protocol

Commit authoritative and projection rows in one backend transaction.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
88
89
90
91
92
93
94
class MemoryUnitOfWork(Protocol):
    """Commit authoritative and projection rows in one backend transaction."""

    async def commit(self, value: MemoryCommit, /) -> Memory:
        """Validate and atomically commit one complete Revision."""

        ...

commit(value) async

Validate and atomically commit one complete Revision.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
91
92
93
94
async def commit(self, value: MemoryCommit, /) -> Memory:
    """Validate and atomically commit one complete Revision."""

    ...

MemoryWritePlan

Bases: BaseModel

A side-effect-free result that can be committed in an outer transaction.

Source code in src/powercontext/builtin/artifacts/memory/protocols.py
53
54
55
56
57
class MemoryWritePlan(BaseModel):
    """A side-effect-free result that can be committed in an outer transaction."""

    result: Memory | None
    commit: MemoryCommit | None

memory_extraction_instructions(profile)

Return the Core-owned instructions for one validated profile.

Source code in src/powercontext/builtin/artifacts/memory/prompts.py
75
76
77
78
def memory_extraction_instructions(profile: MemoryExtractionProfile) -> str:
    """Return the Core-owned instructions for one validated profile."""

    return _INSTRUCTIONS_BY_PROFILE[profile]

memory_extraction_instructions_version(profile)

Return the stable instruction identity for one validated profile.

Source code in src/powercontext/builtin/artifacts/memory/prompts.py
81
82
83
84
def memory_extraction_instructions_version(profile: MemoryExtractionProfile) -> str:
    """Return the stable instruction identity for one validated profile."""

    return _INSTRUCTION_VERSIONS_BY_PROFILE[profile]

Triggers

Pure Trigger contracts for integration-owned runtimes.

PolicyTransition

Bases: BaseModel, Generic[StateT, ActionT_co]

The complete result of one pure Trigger activation.

Source code in src/powercontext/triggers/models.py
13
14
15
16
17
class PolicyTransition(BaseModel, Generic[StateT, ActionT_co]):
    """The complete result of one pure Trigger activation."""

    state: StateT
    actions: tuple[ActionT_co, ...] = ()

Trigger

Bases: Protocol[SignalT_contra, StateT, ActionT_co]

Map one signal and activation state to a pure transition.

Source code in src/powercontext/triggers/protocols.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Trigger(Protocol[SignalT_contra, StateT, ActionT_co]):
    """Map one signal and activation state to a pure transition."""

    def initial_state(self) -> StateT:
        """Return the state used before the first activation."""

        ...

    def activate(
        self,
        signal: SignalT_contra,
        state: StateT,
        /,
    ) -> PolicyTransition[StateT, ActionT_co]:
        """Evaluate a signal without persisting state or executing actions."""

        ...

activate(signal, state)

Evaluate a signal without persisting state or executing actions.

Source code in src/powercontext/triggers/protocols.py
22
23
24
25
26
27
28
29
30
def activate(
    self,
    signal: SignalT_contra,
    state: StateT,
    /,
) -> PolicyTransition[StateT, ActionT_co]:
    """Evaluate a signal without persisting state or executing actions."""

    ...

initial_state()

Return the state used before the first activation.

Source code in src/powercontext/triggers/protocols.py
17
18
19
20
def initial_state(self) -> StateT:
    """Return the state used before the first activation."""

    ...

Errors

Stable failures raised by PowerContext.

ArtifactError

Bases: PowerContextError

Base exception for Artifact lookup and lifecycle failures.

Source code in src/powercontext/errors.py
88
89
class ArtifactError(PowerContextError):
    """Base exception for Artifact lookup and lifecycle failures."""

ArtifactFamilyMismatchError

Bases: ArtifactError, ValueError

Raised when a Revision and Draft belong to different families.

Source code in src/powercontext/errors.py
109
110
111
112
113
114
115
class ArtifactFamilyMismatchError(ArtifactError, ValueError):
    """Raised when a Revision and Draft belong to different families."""

    def __init__(self, artifact: object, draft: object) -> None:
        self.artifact = artifact
        self.draft = draft
        super().__init__("artifact and draft families do not match")

ArtifactNotFoundError

Bases: ArtifactError, LookupError

Raised when an Artifact object is absent from a catalog.

Source code in src/powercontext/errors.py
92
93
94
95
96
97
class ArtifactNotFoundError(ArtifactError, LookupError):
    """Raised when an Artifact object is absent from a catalog."""

    def __init__(self, artifact: object) -> None:
        self.artifact = artifact
        super().__init__("artifact was not found")

InvalidArtifactReferenceError

Bases: ArtifactError, ValueError

Raised when an Artifact reference has an invalid identity or revision.

Source code in src/powercontext/errors.py
100
101
102
103
104
105
106
class InvalidArtifactReferenceError(ArtifactError, ValueError):
    """Raised when an Artifact reference has an invalid identity or revision."""

    def __init__(self, field: str, detail: str) -> None:
        self.field = field
        self.detail = detail
        super().__init__(f"invalid Artifact reference {field}: {detail}")

InvalidSourceAdapterError

Bases: SourceError, TypeError

Raised when an adapter does not satisfy the structural Source contract.

Source code in src/powercontext/errors.py
31
32
33
34
35
36
37
38
class InvalidSourceAdapterError(SourceError, TypeError):
    """Raised when an adapter does not satisfy the structural Source contract."""

    def __init__(self, adapter_type: type[object], field: str, detail: str) -> None:
        self.adapter_type = adapter_type
        self.field = field
        self.detail = detail
        super().__init__(f"invalid Source adapter {_type_name(adapter_type)} {field}: {detail}")

InvalidSourceEntryError

Bases: SourceError, TypeError

Raised when a catalog entry is not a Source value.

Source code in src/powercontext/errors.py
60
61
62
63
64
65
class InvalidSourceEntryError(SourceError, TypeError):
    """Raised when a catalog entry is not a Source value."""

    def __init__(self, actual_type: type[object]) -> None:
        self.actual_type = actual_type
        super().__init__(f"catalog entries must be Source values, got {_type_name(actual_type)}")

InvalidSourceReferenceError

Bases: SourceError, ValueError

Raised when a Source cannot produce a reference required by its contract.

Source code in src/powercontext/errors.py
51
52
53
54
55
56
57
class InvalidSourceReferenceError(SourceError, ValueError):
    """Raised when a Source cannot produce a reference required by its contract."""

    def __init__(self, field: str, detail: str) -> None:
        self.field = field
        self.detail = detail
        super().__init__(f"invalid Source reference {field}: {detail}")

InvalidSourceResultError

Bases: SourceError, TypeError

Raised when an adapter returns a Source outside its declaration.

Source code in src/powercontext/errors.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class InvalidSourceResultError(SourceError, TypeError):
    """Raised when an adapter returns a Source outside its declaration."""

    def __init__(
        self,
        adapter_name: str,
        operation: str,
        expected_type: type[object],
        actual_type: type[object],
    ) -> None:
        self.adapter_name = adapter_name
        self.operation = operation
        self.expected_type = expected_type
        self.actual_type = actual_type
        super().__init__(
            f"Source adapter {adapter_name!r} returned {_type_name(actual_type)} from {operation}, "
            f"expected {_type_name(expected_type)}"
        )

PowerContextError

Bases: Exception

Base exception for stable PowerContext failures.

Source code in src/powercontext/errors.py
6
7
class PowerContextError(Exception):
    """Base exception for stable PowerContext failures."""

RevisionConflictError

Bases: ArtifactError, RuntimeError

Raised when an Artifact write is based on a stale object.

Source code in src/powercontext/errors.py
118
119
120
121
122
123
124
class RevisionConflictError(ArtifactError, RuntimeError):
    """Raised when an Artifact write is based on a stale object."""

    def __init__(self, artifact: object, current: object) -> None:
        self.artifact = artifact
        self.current = current
        super().__init__("artifact is not the latest revision")

SourceAdapterNotFoundError

Bases: SourceError, LookupError

Raised when no adapter owns an exact input or Source class.

Source code in src/powercontext/errors.py
22
23
24
25
26
27
28
class SourceAdapterNotFoundError(SourceError, LookupError):
    """Raised when no adapter owns an exact input or Source class."""

    def __init__(self, route: str, requested_type: type[object]) -> None:
        self.route = route
        self.requested_type = requested_type
        super().__init__(f"no Source adapter is registered for {route} type {_type_name(requested_type)}")

SourceConflictError

Bases: SourceError, ValueError

Raised when immutable catalog routing would be ambiguous.

Source code in src/powercontext/errors.py
41
42
43
44
45
46
47
48
class SourceConflictError(SourceError, ValueError):
    """Raised when immutable catalog routing would be ambiguous."""

    def __init__(self, field: str, value: object) -> None:
        self.field = field
        self.value = value
        rendered = _type_name(value) if isinstance(value, type) else repr(value)
        super().__init__(f"duplicate Source {field}: {rendered}")

SourceError

Bases: PowerContextError

Base exception for Source adapter and access failures.

Source code in src/powercontext/errors.py
10
11
class SourceError(PowerContextError):
    """Base exception for Source adapter and access failures."""

SourceNotFoundError

Bases: SourceError, LookupError

Raised when a Source object is absent from a catalog.

Source code in src/powercontext/errors.py
14
15
16
17
18
19
class SourceNotFoundError(SourceError, LookupError):
    """Raised when a Source object is absent from a catalog."""

    def __init__(self, source: object) -> None:
        self.source = source
        super().__init__("source was not found")

HTTP models

Public HTTP models shared by the Server and Client SDK.

Python Client SDK

Python Client SDK package for the public PowerContext HTTP API.

ClientError

Bases: PowerContextError

Base exception for remote Client SDK failures.

Source code in src/powercontext/client/errors.py
 8
 9
10
11
class ClientError(PowerContextError):
    """Base exception for remote Client SDK failures."""

    request_id: str | None = None

InvalidResponseError

Bases: ClientError

Raised when a successful response violates the public schema.

Source code in src/powercontext/client/errors.py
22
23
24
25
26
27
28
class InvalidResponseError(ClientError):
    """Raised when a successful response violates the public schema."""

    def __init__(self, path: str, *, request_id: str | None) -> None:
        self.path = path
        self.request_id = request_id
        super().__init__(f"response from {path} violated the API schema")

PowerContextClient

Async Python facade for transport-level Server operations.

Source code in src/powercontext/client/client.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
class PowerContextClient:
    """Async Python facade for transport-level Server operations."""

    def __init__(
        self,
        base_url: str,
        *,
        token: str | None = None,
        timeout: float = 10.0,
        http_client: httpx.AsyncClient | None = None,
    ) -> None:
        self._base_url = base_url.rstrip("/")
        self._headers = {"Authorization": f"Bearer {token}"} if token else None
        self._owned_http_client: httpx.AsyncClient | None = None
        if http_client is None:
            self._owned_http_client = httpx.AsyncClient(timeout=timeout)
            self._http_client = self._owned_http_client
        else:
            self._http_client = http_client

    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> None:
        await self.aclose()

    async def aclose(self) -> None:
        """Close only the HTTP client created by this facade."""

        if self._owned_http_client is not None:
            await self._owned_http_client.aclose()

    async def get_liveness(self) -> HealthResponse:
        """Read process liveness."""

        return await self._request(GET_LIVENESS)

    async def get_readiness(self) -> ReadinessResponse:
        """Read deployment readiness checks."""

        return await self._request(GET_READINESS)

    async def get_capabilities(self) -> Capabilities:
        """Read behavior enabled by the assembled runtime."""

        return await self._request(GET_CAPABILITIES)

    async def get_stats(self, request: GetStatsRequest) -> ScopedStats:
        """Read current inventory and bounded usage for one scope."""

        return await self._request(GET_STATS, request)

    async def create_handoff_report_project(
        self,
        request: CreateHandoffReportProjectRequest,
    ) -> ProjectDescriptor:
        """Create one explicit Report Project."""

        return await self._request(CREATE_HANDOFF_REPORT_PROJECT, request)

    async def get_handoff_report_project(
        self,
        request: GetHandoffReportProjectRequest,
    ) -> ProjectDescriptor:
        """Read one current Report Project descriptor."""

        return await self._request(GET_HANDOFF_REPORT_PROJECT, request)

    async def update_handoff_report_project(
        self,
        request: UpdateHandoffReportProjectRequest,
    ) -> ProjectDescriptor:
        """CAS-update one Report Project descriptor."""

        return await self._request(UPDATE_HANDOFF_REPORT_PROJECT, request)

    async def list_handoff_report_projects(
        self,
        request: ListHandoffReportProjectsRequest,
    ) -> ProjectPage:
        """List Report Projects with cursor pagination."""

        return await self._request(LIST_HANDOFF_REPORT_PROJECTS, request)

    async def register_handoff_report_workstream(
        self,
        request: RegisterHandoffReportWorkstreamRequest,
    ) -> WorkstreamDescriptor:
        """Register one existing scope as a Report Workstream."""

        return await self._request(REGISTER_HANDOFF_REPORT_WORKSTREAM, request)

    async def list_handoff_report_workstreams(
        self,
        request: ListHandoffReportWorkstreamsRequest,
    ) -> WorkstreamPage:
        """List Workstreams belonging to one Report Project."""

        return await self._request(LIST_HANDOFF_REPORT_WORKSTREAMS, request)

    async def update_handoff_report_workstream(
        self,
        request: UpdateHandoffReportWorkstreamRequest,
    ) -> WorkstreamDescriptor:
        """CAS-update one Report Workstream descriptor."""

        return await self._request(UPDATE_HANDOFF_REPORT_WORKSTREAM, request)

    async def record_handoff_report_activity(
        self,
        request: RecordHandoffReportActivityRequest,
    ) -> StoredHandoffReportActivity:
        """Record one explicit Report-owned Activity observation."""

        return await self._request(RECORD_HANDOFF_REPORT_ACTIVITY, request)

    async def list_handoff_report_activities(
        self,
        request: ListHandoffReportActivitiesRequest,
    ) -> HandoffReportActivityPage:
        """List one frozen cursor page of Report-owned Activities."""

        return await self._request(LIST_HANDOFF_REPORT_ACTIVITIES, request)

    async def purge_handoff_report_activities(
        self,
        request: PurgeHandoffReportActivitiesRequest,
    ) -> PurgeHandoffReportActivitiesResponse:
        """Purge Report-owned Activities before an observation boundary."""

        return await self._request(PURGE_HANDOFF_REPORT_ACTIVITIES, request)

    async def get_handoff_report_workspace(
        self,
        request: GetHandoffReportWorkspaceRequest,
    ) -> HandoffReportWorkspaceBinding:
        """Read one confirmed Workspace-to-Project binding."""

        return await self._request(GET_HANDOFF_REPORT_WORKSPACE, request)

    async def attach_handoff_report_workspace(
        self,
        request: AttachHandoffReportWorkspaceRequest,
    ) -> HandoffReportWorkspaceBinding:
        """Attach a Workspace to an exact Report Project using CAS."""

        return await self._request(ATTACH_HANDOFF_REPORT_WORKSPACE, request)

    async def detach_handoff_report_workspace(
        self,
        request: DetachHandoffReportWorkspaceRequest,
    ) -> HandoffReportWorkspaceBinding:
        """Detach a Workspace binding using its exact version."""

        return await self._request(DETACH_HANDOFF_REPORT_WORKSPACE, request)

    async def get_handoff_report(self, request: GetHandoffReportRequest) -> HandoffReportResponse | str:
        """Generate the current canonical Handoff Report projection."""

        if request.download:
            raise ValueError("use download_handoff_report when download is true")  # noqa: TRY003
        if request.format.value == "markdown":
            return (await self._request_handoff_report_content(request)).decode("utf-8")
        return await self._request(GET_HANDOFF_REPORT, request)

    async def download_handoff_report(self, request: GetHandoffReportRequest) -> bytes:
        """Download a Markdown or canonical JSON report file."""

        prepared = request.model_copy(update={"download": True})
        return await self._request_handoff_report_content(prepared)

    async def _request_handoff_report_content(self, request: GetHandoffReportRequest) -> bytes:
        payload = TypeAdapter(GET_HANDOFF_REPORT.request_type).dump_python(
            request,
            mode="json",
            by_alias=True,
        )
        try:
            span = ClientSpan.start(GET_HANDOFF_REPORT.operation_id)
            headers = {} if self._headers is None else dict(self._headers)
            span.inject(headers)
            response = await self._http_client.request(
                GET_HANDOFF_REPORT.method,
                f"{self._base_url}{GET_HANDOFF_REPORT.path}",
                json=payload,
                headers=headers,
            )
        except asyncio.CancelledError as error:
            span.finish("cancelled", error=error)
            raise
        except httpx.HTTPError as exc:
            span.finish("failure", error=exc)
            raise TransportError(GET_HANDOFF_REPORT.path) from exc
        except BaseException as error:
            span.finish("failure", error=error)
            raise
        span.finish(
            "success" if response.status_code == GET_HANDOFF_REPORT.success_status else "failure",
            status_code=response.status_code,
        )
        if response.status_code != GET_HANDOFF_REPORT.success_status:
            error = _decode_error(response.content)
            raise ServerResponseError(
                status_code=response.status_code,
                request_id=response.headers.get(REQUEST_ID_HEADER),
                code=None if error is None else error.error.code,
                message=None if error is None else error.error.message,
                details=None if error is None else error.error.details,
            )
        return response.content

    async def capture_content_source(self, request: CaptureContentSourceRequest) -> CaptureContentSourceResponse:
        """Capture raw content as durable Source evidence."""

        return await self._request(CAPTURE_CONTENT_SOURCE, request)

    async def flush_memory(self, request: FlushMemoryRequest) -> FlushMemoryResponse:
        """Run one bounded Source-to-Memory activation."""

        return await self._request(FLUSH_MEMORY, request)

    async def remember_memory(self, request: RememberMemoryRequest) -> MemoryMutationResponse:
        """Save one explicit Memory entry without creating a Source."""

        return await self._request(REMEMBER_MEMORY, request)

    async def search_memory(self, request: SearchMemoryRequest) -> SearchMemoryResponse:
        """Search active Memory entries in one scope."""

        return await self._request(SEARCH_MEMORY, request)

    async def prepare_context(self, request: PrepareContextRequest) -> PreparedContext:
        """Prepare final bounded context for one Agent turn."""

        return await self._request(PREPARE_CONTEXT, request)

    async def prepare_handoff(self, request: PrepareHandoffRequest) -> HandoffDraft:
        """Generate one inspectable Handoff Draft from exact evidence."""

        return await self._request(PREPARE_HANDOFF, request)

    async def activate_handoff(self, request: ActivateHandoffRequest) -> HandoffActivation:
        """Evaluate the standard Handoff Trigger at one Source boundary."""

        return await self._request(ACTIVATE_HANDOFF, request)

    async def finalize_handoff(self, request: FinalizeHandoffRequest) -> PreparedHandoff:
        """Finalize an inspected Handoff Draft for direct transfer."""

        return await self._request(FINALIZE_HANDOFF, request)

    async def commit_handoff(self, request: CommitHandoffRequest) -> CommittedHandoff:
        """Commit one finalized Handoff as a durable milestone."""

        return await self._request(COMMIT_HANDOFF, request)

    async def continue_handoff(self, request: ContinueHandoffRequest) -> HandoffResolution:
        """Resolve temporary or committed Handoff content as untrusted history."""

        return await self._request(CONTINUE_HANDOFF, request)

    async def list_memory_entries(self, request: ListMemoryEntriesRequest) -> ListMemoryEntriesResponse:
        """List active entries, optionally including inactive entries for audit."""

        return await self._request(LIST_MEMORY_ENTRIES, request)

    async def get_memory_entry(self, request: GetMemoryEntryRequest) -> MemoryEntry:
        """Read one exact Memory entry version."""

        return await self._request(GET_MEMORY_ENTRY, request)

    async def revise_memory_entry(self, request: ReviseMemoryEntryRequest) -> MemoryMutationResponse:
        """Revise one exact active Memory entry."""

        return await self._request(REVISE_MEMORY_ENTRY, request)

    async def retire_memory_entry(self, request: RetireMemoryEntryRequest) -> MemoryMutationResponse:
        """Deactivate one exact Memory entry without deleting history."""

        return await self._request(RETIRE_MEMORY_ENTRY, request)

    async def list_memory_changes(self, request: ListMemoryChangesRequest) -> ListMemoryChangesResponse:
        """Read compact Memory Revision changes."""

        return await self._request(LIST_MEMORY_CHANGES, request)

    async def propose_experience(self, request: ProposeExperienceRequest) -> ArtifactCandidate:
        """Submit complete Experience content as a pending Candidate."""

        return await self._request(PROPOSE_EXPERIENCE, request)

    async def generate_experience(self, request: GenerateExperienceRequest) -> GeneratedCandidateResponse:
        """Generate a reviewed Experience Candidate from exact evidence."""

        return await self._request(GENERATE_EXPERIENCE, request)

    async def get_experience(self, request: GetExperienceRequest) -> ExperienceArtifact:
        """Read one exact approved Experience Revision."""

        return await self._request(GET_EXPERIENCE, request)

    async def propose_skill(self, request: ProposeSkillRequest) -> ArtifactCandidate:
        """Submit complete managed Skill content as a pending Candidate."""

        return await self._request(PROPOSE_SKILL, request)

    async def generate_skill(self, request: GenerateSkillRequest) -> GeneratedCandidateResponse:
        """Generate a reviewed managed Skill Candidate from explicit provenance."""

        return await self._request(GENERATE_SKILL, request)

    async def get_skill(self, request: GetSkillRequest) -> SkillArtifact:
        """Read one exact approved managed Skill Revision."""

        return await self._request(GET_SKILL, request)

    async def scan_external_skills(self, request: ScanExternalSkillsRequest) -> ScanExternalSkillsResponse:
        """Refresh the configured host-local external Skill Registry."""

        return await self._request(SCAN_EXTERNAL_SKILLS, request)

    async def list_external_skills(self, request: ListExternalSkillsRequest) -> ListExternalSkillsResponse:
        """List external Skills after live local availability checks."""

        return await self._request(LIST_EXTERNAL_SKILLS, request)

    async def resolve_external_skill(self, request: ResolveExternalSkillRequest) -> ExternalSkillResolution:
        """Resolve one exact local external Skill fingerprint without fallback."""

        return await self._request(RESOLVE_EXTERNAL_SKILL, request)

    async def import_external_skill(self, request: ImportExternalSkillRequest) -> GeneratedCandidateResponse:
        """Snapshot an exact external package and propose a new managed Skill."""

        return await self._request(IMPORT_EXTERNAL_SKILL, request)

    async def list_artifact_candidates(self, request: ListArtifactCandidatesRequest) -> ArtifactCandidatePage:
        """Page current Candidate heads in the Review Inbox."""

        return await self._request(LIST_ARTIFACT_CANDIDATES, request)

    async def get_artifact_candidate(self, request: GetArtifactCandidateRequest) -> ArtifactCandidate:
        """Read the current head of one Candidate."""

        return await self._request(GET_ARTIFACT_CANDIDATE, request)

    async def approve_artifact_candidate(self, request: ApproveArtifactCandidateRequest) -> ArtifactCandidate:
        """Approve the exact current Candidate version."""

        return await self._request(APPROVE_ARTIFACT_CANDIDATE, request)

    async def reject_artifact_candidate(self, request: RejectArtifactCandidateRequest) -> ArtifactCandidate:
        """Reject the exact current Candidate version."""

        return await self._request(REJECT_ARTIFACT_CANDIDATE, request)

    async def revise_artifact_candidate(self, request: ReviseArtifactCandidateRequest) -> ArtifactCandidate:
        """Append a complete replacement Candidate proposal."""

        return await self._request(REVISE_ARTIFACT_CANDIDATE, request)

    async def _request(
        self,
        operation: Operation[_RequestT, _ResponseT],
        request: _RequestT | None = None,
    ) -> _ResponseT:
        json_payload = None
        query_parameters = None
        if request is not None:
            if operation.request_type is None:
                message = f"{operation.operation_id} does not accept a request"
                raise TypeError(message)
            payload = TypeAdapter(operation.request_type).dump_python(
                request,
                mode="json",
                by_alias=True,
            )
            if operation.request_location == "query":
                query_parameters = {key: value for key, value in payload.items() if value is not None}
            else:
                json_payload = payload

        try:
            span = ClientSpan.start(operation.operation_id)
            headers = {} if self._headers is None else dict(self._headers)
            span.inject(headers)
            response = await self._http_client.request(
                operation.method,
                f"{self._base_url}{operation.path}",
                json=json_payload,
                headers=headers,
                params=query_parameters,
            )
        except asyncio.CancelledError as error:
            span.finish("cancelled", error=error)
            raise
        except httpx.HTTPError as exc:
            span.finish("failure", error=exc)
            raise TransportError(operation.path) from exc
        except BaseException as error:
            span.finish("failure", error=error)
            raise
        span.finish(
            "success" if response.status_code == operation.success_status else "failure",
            status_code=response.status_code,
        )

        request_id = response.headers.get(REQUEST_ID_HEADER)
        if response.status_code != operation.success_status:
            error = _decode_error(response.content)
            raise ServerResponseError(
                status_code=response.status_code,
                request_id=request_id,
                code=None if error is None else error.error.code,
                message=None if error is None else error.error.message,
                details=None if error is None else error.error.details,
            )

        try:
            return TypeAdapter(operation.response_type).validate_json(response.content)
        except ValidationError as exc:
            raise InvalidResponseError(
                operation.path,
                request_id=request_id,
            ) from exc

aclose() async

Close only the HTTP client created by this facade.

Source code in src/powercontext/client/client.py
184
185
186
187
188
async def aclose(self) -> None:
    """Close only the HTTP client created by this facade."""

    if self._owned_http_client is not None:
        await self._owned_http_client.aclose()

activate_handoff(request) async

Evaluate the standard Handoff Trigger at one Source boundary.

Source code in src/powercontext/client/client.py
399
400
401
402
async def activate_handoff(self, request: ActivateHandoffRequest) -> HandoffActivation:
    """Evaluate the standard Handoff Trigger at one Source boundary."""

    return await self._request(ACTIVATE_HANDOFF, request)

approve_artifact_candidate(request) async

Approve the exact current Candidate version.

Source code in src/powercontext/client/client.py
504
505
506
507
async def approve_artifact_candidate(self, request: ApproveArtifactCandidateRequest) -> ArtifactCandidate:
    """Approve the exact current Candidate version."""

    return await self._request(APPROVE_ARTIFACT_CANDIDATE, request)

attach_handoff_report_workspace(request) async

Attach a Workspace to an exact Report Project using CAS.

Source code in src/powercontext/client/client.py
298
299
300
301
302
303
304
async def attach_handoff_report_workspace(
    self,
    request: AttachHandoffReportWorkspaceRequest,
) -> HandoffReportWorkspaceBinding:
    """Attach a Workspace to an exact Report Project using CAS."""

    return await self._request(ATTACH_HANDOFF_REPORT_WORKSPACE, request)

capture_content_source(request) async

Capture raw content as durable Source evidence.

Source code in src/powercontext/client/client.py
369
370
371
372
async def capture_content_source(self, request: CaptureContentSourceRequest) -> CaptureContentSourceResponse:
    """Capture raw content as durable Source evidence."""

    return await self._request(CAPTURE_CONTENT_SOURCE, request)

commit_handoff(request) async

Commit one finalized Handoff as a durable milestone.

Source code in src/powercontext/client/client.py
409
410
411
412
async def commit_handoff(self, request: CommitHandoffRequest) -> CommittedHandoff:
    """Commit one finalized Handoff as a durable milestone."""

    return await self._request(COMMIT_HANDOFF, request)

continue_handoff(request) async

Resolve temporary or committed Handoff content as untrusted history.

Source code in src/powercontext/client/client.py
414
415
416
417
async def continue_handoff(self, request: ContinueHandoffRequest) -> HandoffResolution:
    """Resolve temporary or committed Handoff content as untrusted history."""

    return await self._request(CONTINUE_HANDOFF, request)

create_handoff_report_project(request) async

Create one explicit Report Project.

Source code in src/powercontext/client/client.py
210
211
212
213
214
215
216
async def create_handoff_report_project(
    self,
    request: CreateHandoffReportProjectRequest,
) -> ProjectDescriptor:
    """Create one explicit Report Project."""

    return await self._request(CREATE_HANDOFF_REPORT_PROJECT, request)

detach_handoff_report_workspace(request) async

Detach a Workspace binding using its exact version.

Source code in src/powercontext/client/client.py
306
307
308
309
310
311
312
async def detach_handoff_report_workspace(
    self,
    request: DetachHandoffReportWorkspaceRequest,
) -> HandoffReportWorkspaceBinding:
    """Detach a Workspace binding using its exact version."""

    return await self._request(DETACH_HANDOFF_REPORT_WORKSPACE, request)

download_handoff_report(request) async

Download a Markdown or canonical JSON report file.

Source code in src/powercontext/client/client.py
323
324
325
326
327
async def download_handoff_report(self, request: GetHandoffReportRequest) -> bytes:
    """Download a Markdown or canonical JSON report file."""

    prepared = request.model_copy(update={"download": True})
    return await self._request_handoff_report_content(prepared)

finalize_handoff(request) async

Finalize an inspected Handoff Draft for direct transfer.

Source code in src/powercontext/client/client.py
404
405
406
407
async def finalize_handoff(self, request: FinalizeHandoffRequest) -> PreparedHandoff:
    """Finalize an inspected Handoff Draft for direct transfer."""

    return await self._request(FINALIZE_HANDOFF, request)

flush_memory(request) async

Run one bounded Source-to-Memory activation.

Source code in src/powercontext/client/client.py
374
375
376
377
async def flush_memory(self, request: FlushMemoryRequest) -> FlushMemoryResponse:
    """Run one bounded Source-to-Memory activation."""

    return await self._request(FLUSH_MEMORY, request)

generate_experience(request) async

Generate a reviewed Experience Candidate from exact evidence.

Source code in src/powercontext/client/client.py
449
450
451
452
async def generate_experience(self, request: GenerateExperienceRequest) -> GeneratedCandidateResponse:
    """Generate a reviewed Experience Candidate from exact evidence."""

    return await self._request(GENERATE_EXPERIENCE, request)

generate_skill(request) async

Generate a reviewed managed Skill Candidate from explicit provenance.

Source code in src/powercontext/client/client.py
464
465
466
467
async def generate_skill(self, request: GenerateSkillRequest) -> GeneratedCandidateResponse:
    """Generate a reviewed managed Skill Candidate from explicit provenance."""

    return await self._request(GENERATE_SKILL, request)

get_artifact_candidate(request) async

Read the current head of one Candidate.

Source code in src/powercontext/client/client.py
499
500
501
502
async def get_artifact_candidate(self, request: GetArtifactCandidateRequest) -> ArtifactCandidate:
    """Read the current head of one Candidate."""

    return await self._request(GET_ARTIFACT_CANDIDATE, request)

get_capabilities() async

Read behavior enabled by the assembled runtime.

Source code in src/powercontext/client/client.py
200
201
202
203
async def get_capabilities(self) -> Capabilities:
    """Read behavior enabled by the assembled runtime."""

    return await self._request(GET_CAPABILITIES)

get_experience(request) async

Read one exact approved Experience Revision.

Source code in src/powercontext/client/client.py
454
455
456
457
async def get_experience(self, request: GetExperienceRequest) -> ExperienceArtifact:
    """Read one exact approved Experience Revision."""

    return await self._request(GET_EXPERIENCE, request)

get_handoff_report(request) async

Generate the current canonical Handoff Report projection.

Source code in src/powercontext/client/client.py
314
315
316
317
318
319
320
321
async def get_handoff_report(self, request: GetHandoffReportRequest) -> HandoffReportResponse | str:
    """Generate the current canonical Handoff Report projection."""

    if request.download:
        raise ValueError("use download_handoff_report when download is true")  # noqa: TRY003
    if request.format.value == "markdown":
        return (await self._request_handoff_report_content(request)).decode("utf-8")
    return await self._request(GET_HANDOFF_REPORT, request)

get_handoff_report_project(request) async

Read one current Report Project descriptor.

Source code in src/powercontext/client/client.py
218
219
220
221
222
223
224
async def get_handoff_report_project(
    self,
    request: GetHandoffReportProjectRequest,
) -> ProjectDescriptor:
    """Read one current Report Project descriptor."""

    return await self._request(GET_HANDOFF_REPORT_PROJECT, request)

get_handoff_report_workspace(request) async

Read one confirmed Workspace-to-Project binding.

Source code in src/powercontext/client/client.py
290
291
292
293
294
295
296
async def get_handoff_report_workspace(
    self,
    request: GetHandoffReportWorkspaceRequest,
) -> HandoffReportWorkspaceBinding:
    """Read one confirmed Workspace-to-Project binding."""

    return await self._request(GET_HANDOFF_REPORT_WORKSPACE, request)

get_liveness() async

Read process liveness.

Source code in src/powercontext/client/client.py
190
191
192
193
async def get_liveness(self) -> HealthResponse:
    """Read process liveness."""

    return await self._request(GET_LIVENESS)

get_memory_entry(request) async

Read one exact Memory entry version.

Source code in src/powercontext/client/client.py
424
425
426
427
async def get_memory_entry(self, request: GetMemoryEntryRequest) -> MemoryEntry:
    """Read one exact Memory entry version."""

    return await self._request(GET_MEMORY_ENTRY, request)

get_readiness() async

Read deployment readiness checks.

Source code in src/powercontext/client/client.py
195
196
197
198
async def get_readiness(self) -> ReadinessResponse:
    """Read deployment readiness checks."""

    return await self._request(GET_READINESS)

get_skill(request) async

Read one exact approved managed Skill Revision.

Source code in src/powercontext/client/client.py
469
470
471
472
async def get_skill(self, request: GetSkillRequest) -> SkillArtifact:
    """Read one exact approved managed Skill Revision."""

    return await self._request(GET_SKILL, request)

get_stats(request) async

Read current inventory and bounded usage for one scope.

Source code in src/powercontext/client/client.py
205
206
207
208
async def get_stats(self, request: GetStatsRequest) -> ScopedStats:
    """Read current inventory and bounded usage for one scope."""

    return await self._request(GET_STATS, request)

import_external_skill(request) async

Snapshot an exact external package and propose a new managed Skill.

Source code in src/powercontext/client/client.py
489
490
491
492
async def import_external_skill(self, request: ImportExternalSkillRequest) -> GeneratedCandidateResponse:
    """Snapshot an exact external package and propose a new managed Skill."""

    return await self._request(IMPORT_EXTERNAL_SKILL, request)

list_artifact_candidates(request) async

Page current Candidate heads in the Review Inbox.

Source code in src/powercontext/client/client.py
494
495
496
497
async def list_artifact_candidates(self, request: ListArtifactCandidatesRequest) -> ArtifactCandidatePage:
    """Page current Candidate heads in the Review Inbox."""

    return await self._request(LIST_ARTIFACT_CANDIDATES, request)

list_external_skills(request) async

List external Skills after live local availability checks.

Source code in src/powercontext/client/client.py
479
480
481
482
async def list_external_skills(self, request: ListExternalSkillsRequest) -> ListExternalSkillsResponse:
    """List external Skills after live local availability checks."""

    return await self._request(LIST_EXTERNAL_SKILLS, request)

list_handoff_report_activities(request) async

List one frozen cursor page of Report-owned Activities.

Source code in src/powercontext/client/client.py
274
275
276
277
278
279
280
async def list_handoff_report_activities(
    self,
    request: ListHandoffReportActivitiesRequest,
) -> HandoffReportActivityPage:
    """List one frozen cursor page of Report-owned Activities."""

    return await self._request(LIST_HANDOFF_REPORT_ACTIVITIES, request)

list_handoff_report_projects(request) async

List Report Projects with cursor pagination.

Source code in src/powercontext/client/client.py
234
235
236
237
238
239
240
async def list_handoff_report_projects(
    self,
    request: ListHandoffReportProjectsRequest,
) -> ProjectPage:
    """List Report Projects with cursor pagination."""

    return await self._request(LIST_HANDOFF_REPORT_PROJECTS, request)

list_handoff_report_workstreams(request) async

List Workstreams belonging to one Report Project.

Source code in src/powercontext/client/client.py
250
251
252
253
254
255
256
async def list_handoff_report_workstreams(
    self,
    request: ListHandoffReportWorkstreamsRequest,
) -> WorkstreamPage:
    """List Workstreams belonging to one Report Project."""

    return await self._request(LIST_HANDOFF_REPORT_WORKSTREAMS, request)

list_memory_changes(request) async

Read compact Memory Revision changes.

Source code in src/powercontext/client/client.py
439
440
441
442
async def list_memory_changes(self, request: ListMemoryChangesRequest) -> ListMemoryChangesResponse:
    """Read compact Memory Revision changes."""

    return await self._request(LIST_MEMORY_CHANGES, request)

list_memory_entries(request) async

List active entries, optionally including inactive entries for audit.

Source code in src/powercontext/client/client.py
419
420
421
422
async def list_memory_entries(self, request: ListMemoryEntriesRequest) -> ListMemoryEntriesResponse:
    """List active entries, optionally including inactive entries for audit."""

    return await self._request(LIST_MEMORY_ENTRIES, request)

prepare_context(request) async

Prepare final bounded context for one Agent turn.

Source code in src/powercontext/client/client.py
389
390
391
392
async def prepare_context(self, request: PrepareContextRequest) -> PreparedContext:
    """Prepare final bounded context for one Agent turn."""

    return await self._request(PREPARE_CONTEXT, request)

prepare_handoff(request) async

Generate one inspectable Handoff Draft from exact evidence.

Source code in src/powercontext/client/client.py
394
395
396
397
async def prepare_handoff(self, request: PrepareHandoffRequest) -> HandoffDraft:
    """Generate one inspectable Handoff Draft from exact evidence."""

    return await self._request(PREPARE_HANDOFF, request)

propose_experience(request) async

Submit complete Experience content as a pending Candidate.

Source code in src/powercontext/client/client.py
444
445
446
447
async def propose_experience(self, request: ProposeExperienceRequest) -> ArtifactCandidate:
    """Submit complete Experience content as a pending Candidate."""

    return await self._request(PROPOSE_EXPERIENCE, request)

propose_skill(request) async

Submit complete managed Skill content as a pending Candidate.

Source code in src/powercontext/client/client.py
459
460
461
462
async def propose_skill(self, request: ProposeSkillRequest) -> ArtifactCandidate:
    """Submit complete managed Skill content as a pending Candidate."""

    return await self._request(PROPOSE_SKILL, request)

purge_handoff_report_activities(request) async

Purge Report-owned Activities before an observation boundary.

Source code in src/powercontext/client/client.py
282
283
284
285
286
287
288
async def purge_handoff_report_activities(
    self,
    request: PurgeHandoffReportActivitiesRequest,
) -> PurgeHandoffReportActivitiesResponse:
    """Purge Report-owned Activities before an observation boundary."""

    return await self._request(PURGE_HANDOFF_REPORT_ACTIVITIES, request)

record_handoff_report_activity(request) async

Record one explicit Report-owned Activity observation.

Source code in src/powercontext/client/client.py
266
267
268
269
270
271
272
async def record_handoff_report_activity(
    self,
    request: RecordHandoffReportActivityRequest,
) -> StoredHandoffReportActivity:
    """Record one explicit Report-owned Activity observation."""

    return await self._request(RECORD_HANDOFF_REPORT_ACTIVITY, request)

register_handoff_report_workstream(request) async

Register one existing scope as a Report Workstream.

Source code in src/powercontext/client/client.py
242
243
244
245
246
247
248
async def register_handoff_report_workstream(
    self,
    request: RegisterHandoffReportWorkstreamRequest,
) -> WorkstreamDescriptor:
    """Register one existing scope as a Report Workstream."""

    return await self._request(REGISTER_HANDOFF_REPORT_WORKSTREAM, request)

reject_artifact_candidate(request) async

Reject the exact current Candidate version.

Source code in src/powercontext/client/client.py
509
510
511
512
async def reject_artifact_candidate(self, request: RejectArtifactCandidateRequest) -> ArtifactCandidate:
    """Reject the exact current Candidate version."""

    return await self._request(REJECT_ARTIFACT_CANDIDATE, request)

remember_memory(request) async

Save one explicit Memory entry without creating a Source.

Source code in src/powercontext/client/client.py
379
380
381
382
async def remember_memory(self, request: RememberMemoryRequest) -> MemoryMutationResponse:
    """Save one explicit Memory entry without creating a Source."""

    return await self._request(REMEMBER_MEMORY, request)

resolve_external_skill(request) async

Resolve one exact local external Skill fingerprint without fallback.

Source code in src/powercontext/client/client.py
484
485
486
487
async def resolve_external_skill(self, request: ResolveExternalSkillRequest) -> ExternalSkillResolution:
    """Resolve one exact local external Skill fingerprint without fallback."""

    return await self._request(RESOLVE_EXTERNAL_SKILL, request)

retire_memory_entry(request) async

Deactivate one exact Memory entry without deleting history.

Source code in src/powercontext/client/client.py
434
435
436
437
async def retire_memory_entry(self, request: RetireMemoryEntryRequest) -> MemoryMutationResponse:
    """Deactivate one exact Memory entry without deleting history."""

    return await self._request(RETIRE_MEMORY_ENTRY, request)

revise_artifact_candidate(request) async

Append a complete replacement Candidate proposal.

Source code in src/powercontext/client/client.py
514
515
516
517
async def revise_artifact_candidate(self, request: ReviseArtifactCandidateRequest) -> ArtifactCandidate:
    """Append a complete replacement Candidate proposal."""

    return await self._request(REVISE_ARTIFACT_CANDIDATE, request)

revise_memory_entry(request) async

Revise one exact active Memory entry.

Source code in src/powercontext/client/client.py
429
430
431
432
async def revise_memory_entry(self, request: ReviseMemoryEntryRequest) -> MemoryMutationResponse:
    """Revise one exact active Memory entry."""

    return await self._request(REVISE_MEMORY_ENTRY, request)

scan_external_skills(request) async

Refresh the configured host-local external Skill Registry.

Source code in src/powercontext/client/client.py
474
475
476
477
async def scan_external_skills(self, request: ScanExternalSkillsRequest) -> ScanExternalSkillsResponse:
    """Refresh the configured host-local external Skill Registry."""

    return await self._request(SCAN_EXTERNAL_SKILLS, request)

search_memory(request) async

Search active Memory entries in one scope.

Source code in src/powercontext/client/client.py
384
385
386
387
async def search_memory(self, request: SearchMemoryRequest) -> SearchMemoryResponse:
    """Search active Memory entries in one scope."""

    return await self._request(SEARCH_MEMORY, request)

update_handoff_report_project(request) async

CAS-update one Report Project descriptor.

Source code in src/powercontext/client/client.py
226
227
228
229
230
231
232
async def update_handoff_report_project(
    self,
    request: UpdateHandoffReportProjectRequest,
) -> ProjectDescriptor:
    """CAS-update one Report Project descriptor."""

    return await self._request(UPDATE_HANDOFF_REPORT_PROJECT, request)

update_handoff_report_workstream(request) async

CAS-update one Report Workstream descriptor.

Source code in src/powercontext/client/client.py
258
259
260
261
262
263
264
async def update_handoff_report_workstream(
    self,
    request: UpdateHandoffReportWorkstreamRequest,
) -> WorkstreamDescriptor:
    """CAS-update one Report Workstream descriptor."""

    return await self._request(UPDATE_HANDOFF_REPORT_WORKSTREAM, request)

ServerResponseError

Bases: ClientError

Raised when the Server returns a non-success status.

Source code in src/powercontext/client/errors.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class ServerResponseError(ClientError):
    """Raised when the Server returns a non-success status."""

    def __init__(
        self,
        *,
        status_code: int,
        request_id: str | None,
        code: str | None = None,
        message: str | None = None,
        details: dict[str, object] | None = None,
    ) -> None:
        self.status_code = status_code
        self.request_id = request_id
        self.code = code
        self.server_message = message
        self.details = details
        suffix = "" if code is None else f" ({code})"
        super().__init__(f"PowerContext Server returned HTTP {status_code}{suffix}")

TransportError

Bases: ClientError

Raised when no valid HTTP response was received.

Source code in src/powercontext/client/errors.py
14
15
16
17
18
19
class TransportError(ClientError):
    """Raised when no valid HTTP response was received."""

    def __init__(self, path: str) -> None:
        self.path = path
        super().__init__(f"request to {path} failed")