Skip to Content
FeaturesEntity profiles

Entity profiles

consolidate_knowledge() creates a searchable summary from source nodes that mention an entity. It preserves whole source excerpts, dates and provenance within a token budget. The entity association is marked as inferred: a profile quotes what was recorded, including uncertainty and conditions.

from prme import MemoryClient, Scope, StaleProfileError with MemoryClient("./project_memory") as client: client.store( "Aurora deploys from main only after approval.", user_id="alice", scope=Scope.PROJECT, ) client.store( "Aurora keeps nightly backups for 30 days.", user_id="alice", scope=Scope.PROJECT, ) def rebuild() -> int: return client.consolidate_knowledge( user_id="alice", scope=Scope.PROJECT, entity_names=["Aurora"], max_profile_tokens=1000, ) try: created = rebuild() except StaleProfileError: # A source or another rebuild changed during preparation. # This fresh call rereads the current sources; retry at most once here. created = rebuild() response = client.retrieve( "Aurora deployment and backup policies", user_id="alice", scope=Scope.PROJECT, include_cross_scope=False, ) print(response.bundle.render())

MemoryEngine exposes the same arguments as an async method. No extraction LLM is required; the configured embedding provider indexes the profile.

The method returns the number of published profiles. It requires at least two eligible source nodes per name. Earlier generated profiles never count as new source evidence. Names use literal, case-insensitive word boundaries; automatic name discovery is heuristic, so supply known names when available. Whole excerpts that exceed the remaining budget are skipped. If none fits, no replacement is published. The budget includes labels, dates and provenance, measured with the configured packing tokenizer.

Every profile stays within one owner and scope. Omitting scope processes each scope separately. Scope.PROJECT is a scope category, not a named project ID; use separate memory packs to isolate different projects for the same owner. Profile construction scans the active scope in pages, including older sources beyond the newest 5,000 nodes.

Each replacement becomes visible atomically after index preparation. Until it commits, the previous profile stays active. Embedding and storage failures raise an exception. Before staging, the complete profile, source snapshots, fixed UUID and numerical embedding are saved in a checksummed immutable operation. An unchanged retry reuses that preparation; a changed request replaces it explicitly. A successfully published rebuild retires prior profiles for that name.

Prepared work survives restart. The sync client and async engine expose the same owner-scoped recovery methods:

with MemoryClient("./project_memory") as client: jobs = client.profile_jobs(user_id="alice", scope=Scope.PROJECT) result = client.process_profiles( user_id="alice", scope=Scope.PROJECT, limit=20, budget_ms=5000, ) print(result["processed"], result["pending"], result["errors"])

profile_jobs() defaults to pending work; status="complete" and status="abandoned" inspect the other states. Each dictionary includes plan_id (the fixed profile node UUID), scope, attempts and a bounded error code. resume_profile(plan_id, user_id=...) retries one saved preparation without calling extraction or embedding models. It returns None for an unknown or foreign identity. Replaying completed work returns its original identity without restaging or reactivating a subsequently archived profile. Changed dependencies or a replaced preparation raise StaleProfileError; use an explicit consolidate_knowledge() call to prepare current inputs.

process_profiles() reports processed, failed and pending counts plus per-job error codes. Its budget is cooperative between jobs, so one publication may exceed it. Failed jobs move behind unattempted work. Cancellation leaves unfinished preparations available for a later pass. Missing queue rows and ownership entries are reconstructed from validated immutable operations at startup; attempt counts and timestamps are operational diagnostics and reset when those rows are rebuilt.

A multi-entity call contains separate publications; earlier names may succeed before a later one fails. Profiles without enough qualifying sources are retired on an explicit rebuild. Publication completion records graph visibility; predecessor index eviction happens afterward. Use discard_profile(plan_id, user_id=...) to abandon a preparation that should never publish. It returns False for unknown, foreign or completed work, and True once abandoned. This preserves source nodes, published profiles and the immutable preparation. A new consolidation can prepare a fresh identity.

with MemoryClient("./project_memory") as client: for job in client.profile_jobs(user_id="alice", scope=Scope.PROJECT): client.discard_profile(job["plan_id"], user_id="alice") cleanup = client.collect_profile_staging( user_id="alice", scope=Scope.PROJECT, limit=20, budget_ms=5000, ) print(cleanup["collected"], cleanup["remaining"], cleanup["errors"])

Collection verifies that each abandoned preparation uniquely owns its staged inputs and has no graph node or publication receipt. It removes the exact lexical document before its numerical vector, then appends a collection receipt. Failed or cancelled deletion remains discoverable across restart; retries require no model calls. An old managed worker cannot recreate collected entries. The result reports collected preparations, failures, remaining preparations, bounded per-job errors and an optional blocked_reason. Failed attempts move behind unattempted work. The budget applies between preparations. Invalid or incomplete ownership journals block collection, and ambiguous identities or mismatched native entries are retained. PostgreSQL has no external pre-publication index entries; collection validates and acknowledges its abandoned preparation without deleting graph data.

The CLI exposes the same recovery operations for a local memory pack:

prme profile-jobs ./project_memory/memory.duckdb --user-id alice --scope project --format json prme process-profiles ./project_memory/memory.duckdb --user-id alice --scope project --limit 20 --budget-ms 5000 --format json prme resume-profile ./project_memory/memory.duckdb PROFILE_UUID --user-id alice --format json prme discard-profile ./project_memory/memory.duckdb PROFILE_UUID --user-id alice --format json prme collect-profile-staging ./project_memory/memory.duckdb --user-id alice --scope project --format json

Every command requires --user-id. Listing and batch commands accept --scope; omission visits every scope for that owner. profile-jobs --status abandoned lists abandoned preparations. --limit accepts 1–1000; processing and collection budgets are finite nonnegative milliseconds, checked between preparations. Zero budget performs no per-preparation work and still reports remaining work.

JSON output goes to stdout and diagnostics to stderr. Processing failures or blocked collection print the result and exit 1. Unknown or foreign resume IDs both return {"profile_id": null, "resumed": false} with exit 1. Discard returns discarded: false and exit 1 for unknown, foreign or completed work; repeating a successful abandonment returns true. A pass that stops at its limit or budget without failures exits 0; inspect pending or remaining before declaring the queue drained. Invalid command arguments exit 2. Unexpected errors go to stderr.

The positional file is the local target even when PRME_DATABASE_URL is set. These commands are local operator tools: --user-id selects ownership, but is not authentication against another local operator with file access. Use the Python APIs with a PostgreSQL configuration for that backend.

There is no automatic profile scheduler. These explicit Python and CLI operations are separate from the organizer’s consolidate job. Journals and identity reservations remain durable after index collection.


Source: docs/ENTITY-PROFILES.md  in PRME at 58caddb1.