Organize with tags
Each Atomic Memory is an independent Artifact with its own tags. Tags follow the logical identity without changing content revisions, lineage, or embeddings. Other built-in Artifact families also support Scope-local tags.
Current family values are atomic-memory, topic-memory, experience, skill, handoff, profile, and prompt.
The Profile ID is profile; a Prompt ID is its prompt key, such as atomic_memory.extract.
Tags require a persisted Artifact. An unsaved built-in default Prompt has no independent tag set; save a Prompt
Artifact before assigning tags.
With access control enabled, tags follow their target's read and write permissions. A viewer of a shared target can read
its tags but cannot edit them or run a Scope-wide tag query. Queries require scope.read. Tags on Topic Memory Artifacts require scope.read to read and scope.admin to edit. Prompt tags use the Prompt's read permission;
editing requires current scope.admin permission, even if an Artifact owner binding remains after Scope access is revoked.
Profile, Experience, Skill, Handoff, and individual Memory entries use their target's artifact.read / artifact.write
permissions. Insufficient permission returns 403, and revoking a share also revokes tag access.
Manage tags through the Python Client below or the public API. The Dashboard reads saved content and does not provide tag editing or query pages.
Use the Python Client
You need a running Server and an existing Scope and Artifact. Take their IDs from the Dashboard or the corresponding Scope and Artifact APIs; a title is not an ID. Set these non-secret example variables in your terminal:
export POWERCONTEXT_TAG_SCOPE='your-existing-scope-id'
export POWERCONTEXT_TAG_FAMILY='skill'
export POWERCONTEXT_TAG_ARTIFACT='your-existing-artifact-id'Run this with powercontext installed. If the Server requires authentication, provide its bearer token through
POWERCONTEXT_SERVER_AUTH_TOKEN; do not embed it in the script.
import asyncio
import os
from powercontext.client import PowerContextClient
from powercontext.http import QueryArtifactTagsRequest, ReplaceArtifactTagsRequest
async def main():
scope = os.environ["POWERCONTEXT_TAG_SCOPE"]
family = os.environ["POWERCONTEXT_TAG_FAMILY"]
artifact = os.environ["POWERCONTEXT_TAG_ARTIFACT"]
async with PowerContextClient(
"http://127.0.0.1:8000", token=os.getenv("POWERCONTEXT_SERVER_AUTH_TOKEN")
) as client:
current = await client.get_artifact_tags(scope, family, artifact)
if current is None:
raise RuntimeError("An unconditional read must return the current tag set")
saved = await client.replace_artifact_tags(
scope, family, artifact,
ReplaceArtifactTagsRequest.model_validate({"tags": ["customer-a", "release"]}),
expected_etag=current.etag,
)
print(saved.tag_set.model_dump(mode="json")["tags"])
matches = await client.query_artifact_tags(
scope, QueryArtifactTagsRequest.model_validate({"tags": ["CUSTOMER-A"]})
)
print([item.target.model_dump(mode="json") for item in matches.items])
asyncio.run(main())The output contains the saved labels and a matching target. Atomic Memory uses the same get_artifact_tags and
replace_artifact_tags operations with family="atomic-memory" and the returned artifact_id.
Legacy entry tag routes still accept (scope_id, artifact_id, entry_id) and resolve the migrated Atomic identity in
the API adapter. Entries already absent from the old current manifest cannot use this logical lookup; retained old
citations support exact historical reads. New memories have no legacy entry ID. See Atomic Memory.
HTTP and retrieval filters
| Method | Path | Purpose |
|---|---|---|
| GET / PUT | /v1/scopes/{scope_id}/artifacts/{family}/{artifact_id}/tags | Read or replace an Artifact's labels |
| GET / PUT | /v1/scopes/{scope_id}/artifacts/memory/{artifact_id}/entries/{entry_id}/tags | Compatibility read or replacement of a migrated legacy entry's labels |
| POST | /v1/scopes/{scope_id}/artifact-tags/query | Find tagged targets across families |
PUT accepts {"tags":["customer-a","release"]} and requires the ETag returned by GET in If-Match. Missing If-Match
returns 428; stale or wrong-target state returns 412. An unchanged conditional GET returns 304.
tag_digest describes the canonical tag set, but is not the HTTP mutation precondition.
Artifact listing accepts repeated tag parameters and optional tag_match=all|any, for example
/v1/scopes/{scope_id}/artifacts/skill?tag=release&tag=customer-a&tag_match=all.
Supplying tag_match without tag is invalid.
Atomic Memory list/search requests accept these tag fields:
{"tags":["customer-a","release"],"tag_match":"all"}Compatibility routes such as /v1/memory/search retain the old tag_filter request shape. Both filter each independent
memory's tags. Authorization and tag eligibility are applied before candidate limits and reranking; search returns
active memories only. Atomic vector search currently uses exact distance ordering on SQLite and OceanBase, with work
proportional to eligible vector count and dimension.
Tag queries return exact current Artifact references, ordered by family, target type, Artifact ID, and target ID.
Omit families for currently supported families or use, for example,
{"tags":["release"],"families":["atomic-memory","skill"]}. Legacy Memory collections are not a current memory discovery
surface. Tags survive content revisions and Server restarts.
Pass next_cursor unchanged with the same filters, Scope, and caller. Cursors expire after one hour;
invalid or mismatched cursors return 400, expired cursors 410. Each page is internally consistent, but pagination
does not freeze a snapshot across requests.
Label rules and storage
- A target holds at most 32 labels; a filter accepts 1–16 labels.
- Each label has 1–64 Unicode code points, no outer whitespace, and no control, surrogate, or unassigned characters.
- Matching uses NFC normalization followed by Unicode case folding. The submitted display spelling is retained.
Normalized duplicates such as
StraßeandSTRASSEare rejected atomically. A normalized key may not exceed 128 code points. - Tags are Scope-local discovery metadata, not permissions or trusted instructions. They are not added to model prompts, Skill package frontmatter, or publication/import payloads. Published copies start without the source target's tags.
- Inactive memories retain their authoritative tags. Rebuilding active search projections does not delete them.
All assignments live in pc_artifact_tags, with a foreign key to the owning Artifact head. The table retains the full
normalized key and indexes a 32-byte SHA-256 key fingerprint. This preserves the parent column lengths required by
OceanBase while keeping composite indexes below its 3072-byte limit. Matching checks both fingerprint and complete key.
Identical replacement preserves assignment timestamps. Existing Artifacts need no backfill; their initial tag sets are
empty. Include the table in backups and restore it after Artifact heads.
See the Server's HTTP API reference for complete request and response schemas.

