Configuration
pm reads settings from the project tracker root and optional global profile. Use this page for public, user-facing configuration. Use pm config ... list and pm config ... export for the active runtime shape.
Agent Quick Context
- Do not override
PM_PATHfor real repository tracking. - Prefer
--pm-path <repo>/.agents/pmwhen a script must target tracker storage explicitly;--pathremains a compatibility alias and is not a workspace/cwd flag. - Let pm detect supported agent harnesses automatically; set
PM_AUTHORonly when an explicit session-wide identity override is required. - Use
--jsononly when strict parsing is needed. - Use
pm contractsfor current command/schema metadata.
Machine consumers must not infer project-governance values from extension
policy values. pm contracts --json exposes extension modes under
extension_contracts with their exact settings-path scopes and exposes
project-governance values separately under governance_contracts. For example,
governance_contracts.workflow_enforcement_modes is
["off","warn","strict"], while extension_contracts.policy_modes governs
extensions.policy.mode and uses ["off","warn","enforce"].
Tracked documentation work: pm-u9d0.
Configuration Commands
pm config project list
pm config project export --json
pm config project get item-format --json
pm config project set item-format --format toon
pm config project set test-result-tracking --policy enabled
pm config project set untrusted-linked-test-execution --policy disabled
config set <key> <value> also accepts the value as a positional argument; pm routes
it to the right typed flag based on the key (so --policy/--format/--criterion
remain optional for single values):
pm config set telemetry-tracking off # off|on|true|false; global profile
pm config set item-format toon # same as --format toon
pm config set governance-preset strict # same as --policy strict
pm config set definition-of-done "Tests pass" # same as --criterion "Tests pass"
Settings paths are aliases for their existing validated setters: for example,
pm config set governance.preset strict and
pm config set validation.metadata_profile strict. Scalar context settings also
accept paths such as context.default_depth and context.sections.activity.
Context depth values normalize case just like context --default-depth; other
string settings retain their own validation rules.
pm config project list --json lists the project
inventory; use global list for global keys. Both include declared aliases and
nested paths, including context.sections.recently_created and
context.sections.unparented. Context integer leaves use decimal digits in both
configuration commands and package profiles; the inventory includes this syntax
in its summaries alongside numeric bounds. Unknown keys offer up to three
nearest keys and that inspection command. Text bounds long legal domains. The SDK error retains declared aliases and paths
in error.context.recovery.allowed_values; CLI JSON diagnostics may be budgeted.
Tracked by pm-gh1695
(repository-only tracker record, excluded from npm).
The context key has no single value and still uses --default-depth,
--activity-limit, --stale-threshold-days, and --section-<name> flags. Use
--criterion (repeatable) to set more than one criteria-list value at once.
Successful singleton configuration writes append a hash-verified entry to
.agents/pm/history/_workspace.jsonl. Inspect the cross-domain audit trail with
pm activity --id _workspace or verify it with
pm history _workspace --verify.
Scopes:
projectupdates.agents/pm/settings.json.globalupdates the global profile underPM_GLOBAL_PATHor the default global root.
Precedence:
- CLI flags
- environment variables
- project settings
- global settings
- built-in defaults
When settings.json cannot be loaded, pm falls back to built-in defaults and prints a one-time settings_read_fs_error, settings_read_invalid_json, or settings_read_invalid_schema warning to stderr (stdout output is unchanged); run pm health for remediation.
If the existing project settings.json has invalid JSON, a configuration write refuses with settings_write_invalid_existing_json. It leaves the file and workspace history untouched. Repair the syntax in .agents/pm/settings.json, run pm health, then retry the configuration command. The read fallback to defaults does not authorize replacing malformed settings.
If the existing settings.json is valid JSON but fails schema validation (for example an unknown enum value), a configuration write never replaces the file with the defaults fallback (pm-4zpq71, a repository-only tracker record excluded from npm):
- The write applies only its own change. It changes the stored document, and every unrelated stored key is kept.
pm config project set context.next_ranking_policy weightedrepairs that field this way. - Every
pm configsetter repairs its own fields, even to the default value. Each setter assigns the settings paths it writes, so a value that equals the default still replaces the invalid stored value. This applies topm config <scope> set <dotted.key>, the policy and governance setters,contextanditem_format. A key that is serialized only when it differs from the default, such asgovernance.require_close_reason, is removed from the stored document. A cleared value, such as an emptygovernance_create_default_type, is removed the same way. A successful repair reportschanged: true. - A write that would still be invalid refuses. It refuses with
settings_write_invalid_existing_schemaand changes nothing.context.fieldnames the first invalid setting as a dotted path, for examplecontext.next_ranking_policy. A setter that keeps the default of a different invalid field refuses rather than reporting a silentchanged: false. pm config set item_formatrepairs settings before it migrates items. Over an unreadable document it writes the settings first, so a document that would stay invalid refuses before any item file is touched. It then migrates items with the repaired document's own schema and type folders.- SDK writers follow the same rule. This covers every writer that writes a settings object returned by
readSettings. A snapshot cannot tell an assignment equal to the default from an untouched field, so an SDK repair to the default passes the path explicitly:writeSettings(pmRoot, settings, op, { assignedPaths: ["context.next_ranking_policy"] }). WithoutassignedPathssuch a write refuses with the field named rather than claiming success. An empty, reserved (__proto__,constructor,prototype) or non-identifier path segment refuses withsettings_write_assigned_path_invalid. A record key that itself contains a dot cannot be addressed; repair it insettings.json. - Automatic writes never act on a fallback. The mutation preflight's
item_formatpin and its pre-mutation format migration are skipped while the document is unreadable. So are the telemetry installation identity and the first-run consent prompt. The command runs on the defaults with the warning. Package code can checkisSettingsReadFallback(settings)from@unbrained/pm-cli/sdkbefore an unrequested write.
Common Settings
| Setting | Purpose |
|---|---|
id_prefix |
generated item ID prefix, default pm- |
ids.token_length |
random base36 token length for newly minted item ids (4-12, default 4); raise it to shrink cross-branch id collision odds in concurrent multi-agent workflows (see Merge Safety) |
author_default |
configured mutation author before automatic harness detection |
mutation_guard.require_attributed_author |
reject mutations whose effective author remains unknown (default false) |
mutation_guard.secret_guard |
credential-shaped content policy: off, advise, or block (default advise) |
mutation_guard.stale_in_progress_hours |
age threshold for unclaimed in-progress work surfaced by pm health (integer >= 1, default 72) |
item_format |
item storage format (toon writes; legacy markdown is read/migrate only) |
output.default_format |
default renderer, usually toon |
locks.ttl_seconds |
stale lock threshold |
locks.wait_ms |
wait budget before a contended lock returns lock_conflict; item-store mutations, restore and the hierarchy lock restart it on each owner handoff (120 s cap), other locks use it as a fixed total (default 3000; 0 fails fast) |
history.missing_stream |
auto_create or strict_error |
history.compact_policy.enabled |
enable the compaction advisory: pm health warns on over-threshold streams (default false); when disabled, max_entries/trigger are inert |
history.compact_policy.max_entries |
when the policy is enabled, the entry count above which a stream is flagged by pm health and the default pm history-compact --all-over threshold (default 500) |
history.compact_policy.trigger |
policy intent when enabled: health_warn (advisory only) or auto (scheduled sweeps expected) |
testing.record_results_to_items |
persist bounded linked-test summaries |
testing.allow_untrusted_linked_tests |
permit the explicit pm test --allow-untrusted-linked-tests one-run override (default false; does not trust or execute commands by itself) |
validation.sprint_release_format |
warn or strict_error |
validation.parent_reference |
warn or strict_error (strict_error is the built-in default; pm create --allow-missing-parent is the explicit escape hatch) |
item_types.definitions[] |
custom item types and type options |
governance.create_default_type |
default --type used by pm create "title" when --type is omitted (defaults to Task); must resolve to a known item type |
governance.workflow_enforcement |
per-type transition enforcement mode for pm update --status (off default, warn, or strict) |
governance.start_blocker_enforcement |
starting an item (update --status in_progress, claim --start, claim --next --start, start-task) while a declared blocked_by target is open: strict (default; refuse unless --force), warn, or off; claim --next --start skips a refused candidate and claims the next eligible one |
governance.duplicate_detection_mode |
create/copy similarity policy: off, advisory, or strict; the minimal preset uses off, while the default and strict presets use strict |
governance.duplicate_detection_threshold |
inclusive likely-duplicate score from 0 through 1 (default 0.8) |
governance.duplicate_detection_limit |
maximum matches attached to a create/copy advisory from 0 through 20 (default 3) |
schema.type_workflows[] |
per-type allowed status transitions (see Per-Type Workflows below) |
search.* |
search mode, scoring, providers, embedding timeout, and vector settings |
Lock recovery is tracked by pm-02z97f and pm-htiwmd (repository tracker records excluded from npm).
New Linux lock receipts
capture an opaque boot/PID-namespace coordinate and the owner's process start
tick. A matching host can reclaim a provably dead original owner before TTL,
including when its PID has been reused. Foreign hosts, legacy receipts, denied
process probes and unavailable identity remain subject to the existing TTL
policy. A commit gate with a provably dead same-host owner can also be recovered
immediately, before its ordinary age threshold. Its original owner receipt is
retained in locks/recoveries/gate-<generation-hash>/owner.json. These nonempty
generation directories prevent concurrent recoverers from retiring a replacement
gate and must be retained while writers may still hold observations of that
generation. Recovery after the age grace requires a dead PID or verified PID
reuse for a readable owner, and uses the same retained generation protocol.
A live PID with unavailable start identity keeps its gate regardless of age;
elapsed time alone cannot safely distinguish a paused writer from abandonment.
Recorded host coordinates must be verified before recovery; a recorded foreign
host or unavailable recorded host evidence cannot be replaced by a local PID
comparison. Legacy receipts without host_identity use a local PID probe after
the grace period. On shared storage, that probe can retire a gate owned by a
writer on another host whose PID appears dead locally; these legacy receipts
do not provide cross-host recovery safety.
Non-missing owner-file read errors also retain the gate regardless of age.
Lock wait durations use a monotonic clock independently of the
wall-clock timestamps used for TTL policy. This recovery is tracked by
pm-gh1609
(repository tracker record excluded from npm).
If a reboot, PID-namespace change or tracker relocation leaves an unverifiable
locks/<lock-file>.stale-cleanup gate, automatic age-based recovery intentionally
refuses it. pm gc --scope locks does not remove these gate directories.
For manual recovery, first stop every CLI, MCP server, watcher and service that
can write this tracker, including writers on other hosts when storage is shared.
Independently establish that the recorded original owner cannot still run; a
different host hash or an old timestamp alone is insufficient. While exclusive
access is maintained, preserve the complete gate directory and its owner.json
receipt by moving it to a unique backup location outside locks, without
overwriting any existing backup. Keep locks/recoveries evidence intact. Then
retry the original command, run pm health --check-only, and verify the affected
item's history with pm history <id> --verify --strict-exit. If exclusive access
or owner death cannot be established, retain the gate and investigate ownership.
This procedure does not override the remaining lease's TTL or ownership checks.
Early recovery writes an immutable locks/recoveries/*.json intent
receipt before removal; it records the original owner and is an attempted
recovery audit, not proof that a later mutation committed. Keep these local
runtime receipts out of version control.
For item-store mutations (update, close, claim, comments, notes and the other
item-store writes), restore and the hierarchy-integrity lock (parented creates,
parent changes and integrity-sensitive dependency additions) the wait budget
measures stalls, not queue length: while other writers keep acquiring and releasing the
lock, a waiter keeps waiting (up to 120 s total), so many concurrent agents on
one tracker queue instead of failing. Other lock users, including package import
commits and internal short-lived locks, keep a fixed total budget. A lock held by
the same owner for the whole budget still refuses with lock_conflict, whose
recovery reports waited_ms and owner_handoffs. A writer whose lease expired
and was taken over refuses its commit with lock_lost instead of overwriting
the newer write; re-read and apply the change again. For an owner that is
expected to hold the lock longer, set PM_LOCK_WAIT_MS to a larger bounded
millisecond budget, or configure locks.wait_ms, and retry the same operation.
CLI recovery preserves its normalized arguments and quoted values, including
whitespace and empty argument strings. Malformed argument vectors are omitted
instead of being repaired by removing tokens. A retry
advice bundle does not guarantee that another owner will release within that
budget. Inspect pm health --check-only --full and pm gc --scope locks --dry-run before cleanup. Force does not bypass an unexpired lock whose owner is alive
or unverifiable. TTL expiry permits removal regardless of owner liveness. An
expired holder can continue writing, so mutual exclusion beyond TTL is not
guaranteed. Choose a TTL longer than the critical section, or fence protected
resource writes at the caller.
Runtime item types are context primitives. Use pm schema list to inspect the merged registry and pm schema show <Type> to inspect one type's folder, aliases, defaults, required options, and extension provenance. pm init --type-preset agile|ops|research writes reusable domain types into .agents/pm/schema/types.json; this is equivalent to persisted project schema, not an extension-only runtime overlay.
Each scalar setting above is settable via pm config set <key> <value> (no hand-editing of settings.json). The dotted settings.json path is shown in parentheses where it differs from the CLI key:
pm config project set id_prefix task # (id_prefix) IDs become task-xxxx
pm config project set ids_token_length 6 # (ids.token_length) integer 4..12; longer ids for multi-agent collision safety
pm config project set author_default release-bot # (author_default) default mutation author
pm config project set mutation-guard-require-attributed-author true
pm config project set mutation-guard-secret-guard block
pm config project set mutation-guard-stale-in-progress-hours 48
pm config project set output_default_format json # (output.default_format) toon | json
pm config project set locks_ttl_seconds 60 # (locks.ttl_seconds) integer >= 1
pm config project set locks_wait_ms 3000 # (locks.wait_ms) integer >= 0
pm config project set checkpoints_retention_days 30 # (checkpoints.retention_days) integer >= 1; pm gc --scope checkpoints prunes checkpoints older than this many days
pm config project set schema_unknown_field_policy reject # (schema.unknown_field_policy) allow | warn | reject
pm config project set governance-duplicate-detection-mode strict # (governance.duplicate_detection_mode) off | advisory | strict
pm config project set governance-duplicate-detection-threshold 0.8 # (governance.duplicate_detection_threshold) ratio 0..1
pm config project set governance-duplicate-detection-limit 3 # (governance.duplicate_detection_limit) integer 0..20
pm config project set untrusted-linked-test-execution disabled # (testing.allow_untrusted_linked_tests) enabled | disabled
Duplicate governance uses the shared SDK scorer and bounded metadata query
index described in SDK Context Coordination.
Advisory mode keeps exit code zero and adds a structured
similarity_advisory; strict mode requires --allow-duplicate to accept a
threshold match. The strict check and authoritative item/index persistence share
one cross-process mutation lock, so concurrent equivalent creates cannot both
commit. Kebab-case config keys are the documented spelling; existing
underscore aliases remain accepted for compatibility.
Environment Variables
| Variable | Use |
|---|---|
PM_AUTHOR |
optional explicit session-wide mutation author override |
PM_PATH |
override project tracker root for tests or sandboxes |
PM_GLOBAL_PATH |
override global profile root for tests or sandboxes |
PM_LOCK_WAIT_MS |
per-process override for the locks.wait_ms budget (a no-progress window only where that lock uses the progress policy); non-negative integer milliseconds, with 0 restoring fail-fast lock conflicts |
PM_OLLAMA_MODEL |
choose default Ollama embedding model |
PM_DISABLE_OLLAMA_AUTO_DEFAULTS |
disable implicit Ollama search defaults |
PM_CONTEXT_USAGE_DISABLED |
set to 1 to disable the derived context/next usage-feedback ledger with zero filesystem reads or writes |
For command-level tracker targeting, prefer --pm-path <tracker-root>. The old
--path flag is still accepted, but it means the pm tracker storage directory
itself, not the repository workspace. During pm init, explicit tracker paths
that look like workspace roots are rejected unless --force is provided.
Initialization maintains the repository's runtime-cache .gitignore fence for
every tracker sharing that repository. Initializing or reinitializing a nested
tracker preserves the enclosing tracker's exclusions and unrelated user rules.
Equivalent physical and symlink-addressed paths share the same containment
check, including tracker directories that initialization has not created yet.
A symlink leading outside the repository remains an external tracker and does
not add rules here. This boundary is tracked by
pm-p8s5k7e6lhda.
Custom tracker paths are literal Git paths, including spaces and wildcard
characters. An intentional root-layout tracker uses repository-root exclusions.
Paths containing line breaks are refused because .gitignore cannot safely
represent them. Curated search/eval-queries.json files remain trackable in
each tracker. See merge safety.
Implicit discovery checks the current directory and its ancestors up to the
nearest .git directory or file, including worktrees and submodules. An
uninitialized repository selects its own .agents/pm and returns initialization
recovery instead of reading or changing an enclosing repository's tracker.
Ordinary subdirectories still share their repository's tracker, including
root-layout trackers. To deliberately share a tracker across repository
boundaries, pass --pm-path <tracker-root> or set PM_PATH explicitly.
This shared SDK discovery policy also applies to CLI and MCP commands.
Directory aliases follow physical ancestry, including missing descendants of
an existing alias. Unexpected directory or .git lookup errors stop discovery
instead of falling through to an enclosing tracker.
Tests should set both PM_PATH and PM_GLOBAL_PATH to temporary directories. The wrapper node scripts/run-tests.mjs ... does that automatically.
Telemetry environment variables
Telemetry is enabled by default. Use pm config set telemetry-tracking off to opt out persistently (see Common Settings). Consent and the event endpoint live only in the global profile:
- A scope-less
telemetry-trackingcall targets the global profile. - Project-scope
getandsetrefuse. Projectlistandexportomit the key. pm initasks only while no choice is recorded in the global profile. It keeps a choice another process records while its prompt is open. It reports the global value and writes no projecttelemetryblock.- An older project
telemetryblock is ignored.
When enabled, these environment variables tune runtime behaviour. All boolean knobs accept 1, true, yes, or on (case-insensitive); any other value leaves the knob off.
| Variable | Values | Use |
|---|---|---|
PM_TELEMETRY_DISABLED |
boolean | Hard-disable all telemetry for this process, ignoring settings. |
DO_NOT_TRACK |
boolean | Hard-disable event capture, identity creation, flush workers, endpoint probes, and Sentry, regardless of persisted preferences. |
PM_TELEMETRY_SEND_TEST_EVENTS |
boolean | Explicitly allow inferred test events. Hard opt-outs still take precedence. |
PM_TELEMETRY_READ_SAMPLE_RATE |
number greater than 0 and at most 1 |
Override automatic successful-read sampling; 1, empty, or invalid values retain every invocation. Omit for automatic frequency bounding. |
PM_NO_TELEMETRY |
boolean | Alias for PM_TELEMETRY_DISABLED (honoured by the same checks). |
PM_TELEMETRY_OTEL_DISABLED |
boolean | Disable only OTLP trace-span export; the event queue still flushes. |
PM_TELEMETRY_INLINE_FLUSH |
boolean | Flush the queue and OTLP spans inline instead of dispatching the detached worker. Mainly for tests; normal use relies on the background worker. |
PM_TELEMETRY_SOURCE_CONTEXT |
user | automation | test | dogfood |
Override the inferred source context recorded on each event. Unrecognized nonempty values use inferred context with source_context_source=env_override_rejected; the raw value is never emitted. |
PM_TELEMETRY_HTTP_TIMEOUT_MS |
integer milliseconds | Bound each background event or OTLP request (default 20000, clamped to 1000–25000 to stay below the worker lock TTL). |
PM_TELEMETRY_INGEST_KEY |
string | Sent as the x-pm-telemetry-key header on queue flushes; never logged. |
PM_TELEMETRY_ENDPOINT |
http(s) URL | Deliver events to this collector instead of the configured endpoint. Events captured under it go to a queue and state file of their own that only a process with the same value reads or reports; a later flush removes them after a day idle. pm telemetry clear removes them too, but it also revokes consent and deletes every telemetry queue and state file of the profile. An empty value uses the configured endpoint; any other value that is not an http(s) URL disables delivery. URL credentials are sent as Basic auth, by pm health --check-telemetry too. |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
URL | OTLP/HTTP traces endpoint for command spans. Takes precedence over the base endpoint. |
OTEL_EXPORTER_OTLP_ENDPOINT |
URL | Base OTLP endpoint; the traces endpoint is derived by appending /v1/traces. |
OTEL_SERVICE_NAME |
string | service.name attribute on exported spans (defaults to pm-cli). |
Unrecognized source-context overrides produce one bounded warning per process on stderr, naming the accepted values without echoing the rejected input. JSON invocations retain parseable stdout and receive the warning on stderr. Explicit quiet mode suppresses the warning; events and spans still carry the fixed rejection marker. Empty overrides mean no override. Existing consent rules continue to suppress all capture when telemetry is disabled.
Event and OTLP queue appends and reconciliation share an installation-level
mutex. Successful delivery, retry updates, and retention pruning reread the
current queue while holding that mutex, so another updated CLI process cannot
append into a file about to be replaced. Network delivery runs outside the
mutex. Each foreground start, finish, or error capture uses a 250 ms
lock-acquisition budget by default; queue maintenance, clear, and flush initialization
retain a five-second default. PM_LOCK_WAIT_MS explicitly overrides these
budgets. These are per-acquisition contention budgets, not total operation
deadlines: queued in-process transactions and filesystem work can add latency.
The existing 60-second stale-lock recovery policy applies. Telemetry remains best
effort when storage is unavailable or contention exhausts the wait budget;
collector success and an empty queue are separate observations, not a guarantee
of exactly-once delivery. Upgrade concurrent CLI processes together: older
versions do not participate in the installation mutex.
Start and error capture recheck process opt-outs and saved consent under the
installation mutex before initializing identity, and retain that mutex through
immediate event writes. Completion rechecks both consent sources and its saved
installation identity under the same mutex through deferred start, finish,
and span writes. telemetry clear uses this transaction boundary, so it either
removes a completed capture or prevents that capture from appending afterward.
Flush identity initialization also revalidates consent under the mutex; network
delivery and flush scheduling remain outside it. Preference changes are
evaluated at the capture transaction's consent check. Re-enabling after a clear
does not revive an invocation's old identity. These checks cannot recall
requests already dispatched to a collector.
Interaction rules:
DO_NOT_TRACK/PM_TELEMETRY_DISABLED/PM_NO_TELEMETRYshort-circuit everything, including OTLP export, regardless of the other knobs.PM_TELEMETRY_HTTP_TIMEOUT_MSapplies only to the detached flush worker in normal use. It covers DNS, each IPv4/IPv6 connection attempt, TLS, and response completion. The upper bound preserves the 60-second worker-lock safety margin across the sequential event and OTLP phases; it does not make foreground commands wait for the network.- OTLP span export only happens when telemetry is enabled,
PM_TELEMETRY_OTEL_DISABLEDis off, and a traces endpoint is configured. By default spans are persisted to a bounded queue and exported by the detached, unref'd flush worker so commands exit promptly even when the traces endpoint is unreachable.PM_TELEMETRY_INLINE_FLUSH=1is the explicit test-oriented exception that performs the flush inline. pm health --check-telemetry --jsonsurfaces flush and OTLP export diagnostics (pending_otel_spans,last_otel_attempt_at,last_otel_success_at,last_otel_failure_at,last_otel_failure_error) and the activeenv_overrides(includingtelemetry_inline_flushandtelemetry_source_context) so agents can self-diagnose a stalled endpoint.- An explicit
PM_AUTHORoverride adds a privacy-preserving agent-identity dimension tocommand_start/command_finishevents so overridden invocations can be segmented in dashboards without leaking the raw author string. Atredacted/maxcapture the events carryauthor_context_hash— the same installation-id-keyed one-way SHA-256 used forpm_root_hash/cwd_hash, so the same author hashes consistently within an installation but differently across installations. Atminimalcapture only a booleanhas_author_contextis emitted. The rawPM_AUTHORvalue is never exported.
Test environments inferred from VITEST, VITEST_WORKER_ID, NODE_TEST_CONTEXT
(native node --test children), or NODE_ENV=test suppress telemetry and Sentry
before identity creation. A valid explicit PM_TELEMETRY_SOURCE_CONTEXT,
including test, or PM_TELEMETRY_SEND_TEST_EVENTS=1 permits intentional test
delivery, so a test that only wants the label must also set
PM_TELEMETRY_DISABLED=1. Point such tests at a local collector: set
PM_TELEMETRY_ENDPOINT for events, SENTRY_DSN for errors, and
OTEL_EXPORTER_OTLP_* for spans. Inferred tests use inline telemetry flushing even
after an opt in, so CLI delivery settles before process exit and disposable-root
cleanup. SDK telemetry completion also awaits inline delivery before returning.
Neither setting overrides a hard opt-out. pm telemetry status exposes effective enabled, persisted
configured_enabled, and env_overrides so an empty queue is distinguishable
from disabled delivery.
SDK consumers that previously used TelemetryStatusSummary.enabled to display the saved preference should read configured_enabled instead. Use enabled to determine whether this process permits delivery.
At redacted and max capture, events include a bounded agent_harness dimension (claude-code, codex, pi, opencode, cursor, aider, gemini-cli, ci, other, or none) and a boolean ci. Installation-keyed hashes remain available; raw author and model names remain private. Minimal capture omits both new dimensions.
Repeated core reads are sampled automatically after the first ten invocations of a command in each UTC minute. The nth invocation has successful-pair inclusion probability min(1, (10 / n)^2), keeping expected successful pairs below twenty per command per minute. This is a statistical bound, not a hard cap; failures remain retained. Counters are shared across processes under the installation queue mutex, reset at the next minute, and store only the eight fixed command names and numbers in runtime/telemetry/read-sampling.json. Unavailable state retains the invocation.
Eligible commands are stats, ops stats, list, get, next, context, activity, and aggregate. The runtime must establish core ownership: --no-extensions provides an explicit boundary, while an active core command may coexist with unrelated extension commands. Matching handlers or parsers, and active hooks, services, preflights, renderers, migrations, search/vector adapters, or assurance providers prevent sampling. Unknown ownership and mutations retain every invocation. Set PM_TELEMETRY_READ_SAMPLE_RATE=1 to disable automatic sampling, or a number greater than zero and below one for a fixed rate; explicitly empty or invalid values also retain every invocation.
Successful sampled reads defer their start until completion so both lifecycle events are retained or dropped together; abruptly terminated sampled reads have no event pair. Retained pairs carry numeric sample_rate, and OTLP spans carry pm.sample_rate; estimate population totals with weight 1 / sample_rate (missing means 1). Very small inclusion probabilities produce high-variance estimates. Failed sampled reads carry probability 1. Local queue statistics describe retained observations, not weighted population estimates.
pm telemetry stats reads the local queue and reports, per command bucket, latency percentiles (duration_p50_ms/duration_p95_ms/duration_max_ms, nearest-rank over command_finish duration_ms), outcome rates (ok_count/error_count/error_rate; a finish event whose ok is missing or not strictly true is counted conservatively as an error), and command_resolution_counts. These are an always-available, zero-network performance and reliability signal for the most recent queued window.
Background completion delivery is tracked by pm-j8spis. The detached uploader continues draining due events and OTLP spans appended during an upload, so completion delivery does not require another command. It releases the upload lock between batches and rechecks consent and identity. Future retries remain queued, and unchanged eligible work stops the drain; foreground commands still perform no export network I/O.
Sentry environment variables
Tracing and privacy delivery: pm-58yzil and pm-geywoc.
Crash and error diagnostics are reported to Sentry only when telemetry is enabled and the process is not opted out. These environment variables tune the Sentry client.
| Variable | Values | Use |
|---|---|---|
PM_SENTRY_DISABLED |
boolean | Disable Sentry error reporting for this process (the Sentry SDK is not even imported). PM_TELEMETRY_DISABLED / PM_NO_TELEMETRY also disable it. |
SENTRY_DSN |
URL | Override the destination DSN. Defaults to the bundled pm-cli project DSN. |
SENTRY_ENVIRONMENT |
string | Override the reported environment tag. Defaults to test under Vitest, ci when CI is set, otherwise production. |
PM_SENTRY_TRACE_COMMANDS |
boolean | Explicitly initialize Sentry for successful commands. Defaults to off, preserving lazy startup. All consent opt-outs and test isolation still apply; a zero trace sample rate avoids initialization. |
SENTRY_TRACES_SAMPLE_RATE |
number 0–1 |
Fraction of command spans sampled for performance tracing. Defaults to 0.2 (20%). Set to 1 for full-trace performance debugging or 0 to disable tracing. Non-numeric or out-of-range values fall back to the default. |
Sentry is hard-disabled under Vitest (
VITEST/VITEST_WORKER_ID), so these knobs are no-ops inside the test suite.
For a bounded diagnostic invocation use
PM_SENTRY_TRACE_COMMANDS=1 SENTRY_TRACES_SAMPLE_RATE=1 pm context --limit 3.
Completion flushes buffered spans within the existing three-second SDK timeout.
The sample-rate variable alone does not enable successful-command initialization.
Configured telemetry opt-outs also prevent it. The CLI skips command context and
span creation when consent is disabled, including when an embedding caller has
already initialized Sentry. SDK callers use
ensureSentryCommandTracing(telemetryEnabled) from @unbrained/pm-cli/sdk,
passing an explicit true only after confirming effective consent. Start a
command span only when the returned permission is true; an existing client
also requires the current opt-in, sampling and process policy. Other runtime
values keep initialization disabled.
Consent is checked again at command completion. A command that disables
telemetry discards its span; SDK callers can use sentryDiscardCommandSpan()
to clear a pending span without exporting it.
A failed completion-time settings read also discards the span, so optional
diagnostics cannot turn a successful command into a failure.
If the SDK loader or initializer fails, tracing remains unavailable for that
process and the command continues. The partial client is cleared and the
failed initialization is not retried.
Sentry sends streamed spans; its span and log hooks apply the same scrubbing
as errors and breadcrumbs. A final envelope hook scrubs serialized log attributes
after the SDK merges current and isolation scopes, preserving safe typed values
without changing shared scopes. Template parameter attributes are always redacted.
Command context retains option keys and redacted
argument slots, including text that resembles a flag. Automatic user, cookie, HTTP header/body/query, AI, database,
GraphQL, local-variable and source-context collection is explicitly
disabled. Command taxonomy and outcome attributes remain available on spans.
URL credentials never reach telemetry or Sentry, whatever the host looks like
(pm-5htunt;
tracker record excluded from npm). Before any other sanitizer runs, each
command argument is treated as one token and each other string as
whitespace-delimited words. In every such token, the span between the first
: and the last @ is replaced with [redacted], and any scheme slashes
are kept. The rule is conservative so that it is complete: a single-label
host, host:port, a backslash authority or a password with any characters is
covered. A scoped package name after a colon is hidden too. The CLI also
registers each secret of the running invocation, including its shell and
JSON escaping. Any echoed run that shares a six-character fragment with one
of them is removed, so a split, truncated or re-cased echo exposes at most
five consecutive characters (more only for a secret beyond the 65,536-fragment
budget). The command taxonomy of an unknown
command is derived from the already-redacted token.
The separate local event and OTLP queues retain their existing formats. Turning off command tracing does not require deleting or rewriting those queues.
Linked commands launched by pm test --run default
SENTRY_ENVIRONMENT=test, even when the parent process is classified as
production. This keeps sandbox failures out of production diagnostics. An
explicit run-level or per-test env_set=SENTRY_ENVIRONMENT=<name> directive
still overrides the default when a test intentionally targets another
environment.
Item Storage Format
TOON is the default:
pm config project set item-format --format toon
Markdown item files are treated as legacy migration input only. Mutations always write TOON files, and history stays JSONL.
Output Format
Most commands default to sparse TOON:
pm list --status open --limit 10
Use JSON for strict machine parsing:
pm get <id> --json
pm get <id> --full --json
pm contracts --json
With JSON output, preflight advisories are deferred until the command outcome.
A failed command includes their stable codes in the error envelope's warnings
array on stderr, preserving one parseable error and its original exit code.
Additional explanatory or remediation text is retained in warning_details.
A successful command emits one { "type": "warning", "warnings": [...] }
diagnostic on stderr when needed; its result remains on stdout. Human output
keeps immediate warning text. See
pm-gh1602
(repository tracker record excluded from npm).
--output-format json|toon selects encoding on both reads and mutations.
Item collection and lifecycle commands accept --format json|toon as a
compatibility alias, before or after the command:
pm comments <id> --format json
pm --format json create Task "Follow up" --create-mode progressive
Command-specific formats retain their meaning, including list --format csv,
deps --format graph, and config set item-format --format toon. Use
--output-format to select the envelope encoding alongside those options.
Package commands retain their declared flag vocabulary.
pm calendar (provided by the optional calendar package — pm install calendar --project) defaults to markdown because date-centric summaries are easier to scan in that format.
Validation Policies
pm config project set sprint-release-format-policy --policy warn
pm config project set parent-reference-policy --policy strict_error
pm config project set history-missing-stream-policy --policy auto_create
pm config project set test-result-tracking --policy enabled
pm config project set untrusted-linked-test-execution --policy disabled
Use standalone checks when validating a repository:
pm validate --check-resolution --check-history-drift
pm validate --check-files --scan-mode tracked-all
pm health --check-only --summary --json
Per-Type Workflows
Restrict which status transitions are allowed for a given item type. Rules live in
schema/workflows.json alongside the lifecycle workflow block, and enforcement is
gated by governance.workflow_enforcement (default off, so existing projects are
unaffected until you opt in).
// .agents/pm/schema/workflows.json
{
"workflow": {
"open_status": "open",
"close_status": "closed",
"canceled_status": "canceled",
},
"type_workflows": [
{
"type": "Story",
"allowed_transitions": [
["open", "in_progress"],
["in_progress", "closed"],
],
},
],
}
pm config project set governance-workflow-enforcement strict # off | warn | strict
pm config project set governance-create-default-type Issue # default create type
Semantics:
- A type with no
type_workflowsentry is unrestricted (every transition allowed). - A type with an entry allows only the listed
[from, to]pairs.from/toare matched case-insensitively and resolved through the status registry's aliases. - A same-status no-op (
from === to) is always allowed. workflow_enforcement: strictrejects a disallowed transition (including transitions toward the close status, gated before close-routing) with the allowed-transition hint.workflow_enforcement: warnstill applies the change but adds aworkflow_transition_not_allowedwarning to the update result.workflow_enforcement: off(default) ignores alltype_workflowsrules.
Search Configuration
Keyword search is always available:
pm search "release docs" --mode keyword --limit 10
search.tuning controls deterministic lexical weighting in keyword mode and the lexical component of hybrid mode. Default runtime weights:
title_exact_bonus=10title_weight=48,description_weight=5,tags_weight=6,status_weight=2body_weight=1,comments_weight=1,notes_weight=1,learnings_weight=1reminders_weight=2,events_weight=2dependencies_weight=3,linked_content_weight=1
The title is the identity-bearing summary and therefore carries substantially
more weight than append-only annotations. This prevents a long-lived roadmap or
rollup item from outranking the item that owns the queried concept merely
because the rollup repeats the same terms across many comments. The default is
measured by the real-corpus pm eval gate; projects can still override every
weight through search.tuning.
Semantic and hybrid search can use built-in OpenAI-compatible or Ollama providers plus vector stores such as Qdrant or LanceDB. If local Ollama is available and semantic settings are unset, pm can resolve local defaults automatically.
Offline BM25 provider (pm-75k9)
For air-gapped, CI, or zero-setup environments there is a built-in BM25 lexical ranker that powers --semantic/--hybrid search entirely in-process — no embedding service or vector store required. Select it via search.provider:
pm config project set search_provider bm25 # always use BM25 for semantic/hybrid
pm config project set search_provider auto # use BM25 only when no embedding provider is configured
bm25always uses the offline ranker for semantic/hybrid queries, even when an embedding provider is set.autofalls back to BM25 only when no embedding provider (and no extension search provider) is available; the search result then carries asearch_<mode>_offline_bm25:no_embedding_provider:using_lexical_bm25warning.- Any other value (e.g.
openai,ollama, or an extension provider name) keeps the existing embedding/vector path.
Tune BM25 ranking with two persisted, validated knobs:
search.bm25.k1(number>= 0, default1.2) — term-frequency saturation; higher values let repeated terms keep accruing weight, lower values saturate sooner.search.bm25.b(number0..1, default0.75) — document-length normalization;0disables it,1fully normalizes by length.
pm config project set search_bm25_k1 1.5
pm config project set search_bm25_b 0.5
BM25 quality is lexical — a strong baseline, but below true dense retrieval; configure an embedding provider when semantic similarity matters. Measure either path against a golden-query set with pm eval (see COMMANDS.md).
search.hybrid_semantic_weight (number 0..1, default 0.7) is the persistent default blend used in hybrid mode: it weights the semantic (vector) score against the lexical score during score fusion — higher favours semantic retrieval, lower favours keyword matching. A per-query pm search --semantic-weight <value> overrides it for a single query; when the flag is omitted the persistent setting applies. Set it once with:
pm config set search_hybrid_semantic_weight 0.85
search.score_threshold (number, default 0) drops hits scoring below the floor (after hybrid fusion, before limit truncation). A per-query pm search --min-score <float> overrides it for a single query. search.max_results (default 50) caps returned hits across all modes when --limit is omitted, so broad keyword queries no longer return every match.
For local Ollama or slower embedding providers, tune search.embedding_batch_size, search.embedding_timeout_ms, and search.scanner_max_batch_retries in project config before assuming semantic search is broken. Keyword search remains the fast baseline while semantic indexing catches up.
search.embedding_corpus_max_characters optionally overrides provider defaults for corpus truncation (8000 for OpenAI-compatible providers, 3200 for Ollama). Invalid values fall back to the provider default and emit search_embedding_corpus_max_characters_invalid:using_provider_default warnings in semantic indexing workflows.
search.corpus_fields is an optional string array that controls which item fields are embedded into the semantic search corpus. When unset or empty, the full default field set is embedded (backward compatible). When set, only the named fields are embedded — letting teams opt structured signals in/out for token efficiency so queries like "high priority bugs blocking release" can match on those fields. The default (and full set of valid) field names are:
title, description, tags, status, type, priority, assignee, parent,
goal, value, why_now, risk, confidence, estimated_minutes,
acceptance_criteria, resolution, expected_result, actual_result,
body, comments, notes, learnings, reminders, events, dependencies, plan
Unknown names in the list are ignored. Optional structured fields are only embedded when present/non-empty on an item (absent fields add no tokens). Because this key is an array, it is not settable via pm config set (which handles scalar keys) — hand-edit .agents/pm/settings.json:
{
"search": {
// embed only the high-signal fields for compact, focused vectors
"corpus_fields": [
"title",
"description",
"tags",
"type",
"priority",
"status",
"body",
],
},
}
Re-embed note: changing corpus_fields (or upgrading to a build that embeds new fields) changes the embedding input, so existing items are flagged stale and re-embedded on the next semantic refresh / pm reindex. This is expected and self-healing.
Advanced relevance tuning is opt-in:
search.query_expansion.enabledenables pre-embedding query expansion for semantic/hybrid runs.search.query_expansion.providerselects the expansion provider name. Built-in names areopenaiandollama; extension providers can exposequeryExpansion/query_expansionhooks under the same provider name.search.rerank.enabledenables post-retrieval reranking for hybrid mode.search.rerank.modeloverrides the model used by built-in rerank execution.search.rerank.top_kcontrols how many top hybrid candidates are reranked.
When query expansion or rerank providers fail, pm search degrades gracefully and keeps the query runnable; warning codes are emitted in the result payload.
pm search filter metadata now reports query_expansion_* and rerank_* fields so automation can detect when advanced tuning is active.
search.default_mode decides the mode a bare pm search runs in (--mode always wins):
| Value | Behavior |
|---|---|
auto |
default; hybrid when search.provider names the active embedding provider and a vector store or extension adapter resolves (or an extension search provider is active), keyword otherwise |
keyword |
pin exact-token ranking for every bare search |
semantic, hybrid |
pin the semantic path; an unreachable provider still degrades to keyword hits with a search_semantic_fallback warning |
Every search result reports mode_source (explicit, settings, or auto) beside the effective mode.
Mutation commands invalidate keyword search caches immediately. Semantic vector refresh is controlled by search.mutation_refresh_policy:
| Policy | Behavior |
|---|---|
semantic_configured |
default; refresh vectors during mutations only when semantic provider/store settings are explicitly configured |
cache_only |
fastest writes; invalidate keyword caches and leave vector refresh to pm reindex or pm health --refresh-vectors |
semantic_auto |
also apply implicit local Ollama/LanceDB defaults during mutations |
Useful commands:
pm config project set search_default_mode keyword
pm config project set context_next_ranking_policy weighted # pm next: priority_floor (default) or weighted
pm config project set search_mutation_refresh_policy cache_only
pm config project set search_embedding_corpus_max_characters 12000
pm config project set search_query_expansion_enabled true
pm config project set search_query_expansion_provider openai
pm config project set search_rerank_enabled true
pm config project set search_rerank_model text-embedding-3-small
pm config project set search_rerank_top_k 20
pm search "calendar reminders" --mode hybrid --semantic-weight 0.35 --limit 10
pm reindex --mode hybrid --progress
pm health --check-only
Custom search providers
pm supports any OpenAI-compatible API or Ollama-style provider via configuration. No code changes needed. Each leaf below can be set with pm config <scope> set <key> <value> (the corresponding settings.json dotted path is shown in parentheses).
Ollama (local, default for offline embedding):
pm config project set ollama_base_url http://localhost:11434
pm config project set ollama_model nomic-embed-text
pm config project set search_provider ollama
OpenAI:
pm config project set openai_base_url https://api.openai.com/v1
pm config project set openai_api_key '<OPENAI_API_KEY>'
pm config project set openai_model text-embedding-3-small
pm config project set search_provider openai
The base URL defaults to empty, so it must be set explicitly even for the canonical OpenAI endpoint.
LM Studio (OpenAI-compatible) — point pm at LM Studio's local OpenAI-compatible endpoint:
pm config project set openai_base_url http://localhost:1234/v1
pm config project set openai_model nomic-embed-text-v1.5
pm config project set search_provider openai
Set openai_api_key to any non-empty value if LM Studio is configured to require one.
vLLM (OpenAI-compatible) — point pm at a vLLM server serving an embedding model:
pm config project set openai_base_url http://localhost:8000/v1
pm config project set openai_model BAAI/bge-large-en-v1.5
pm config project set search_provider openai
Vector store — choose where vector embeddings live. LanceDB is file-backed and zero-setup; Qdrant runs as a service:
pm config project set vector_store_adapter lancedb
pm config project set vector_store_collection_name pm_items
pm config project set lancedb_path .agents/pm/search/lancedb
# or, with Qdrant:
pm config project set vector_store_adapter qdrant
pm config project set vector_store_collection_name workspace_items
pm config project set qdrant_url http://localhost:6333
pm config project set qdrant_api_key '<QDRANT_API_KEY>' # omit on unauthenticated dev servers
After changing any of these, run pm reindex --mode hybrid so the vector index reflects the new provider/store. pm search ... --mode semantic|hybrid emits a vector_index_stale warning when items have been modified since the last reindex.
Refresh contract (important for agents): by default a mutation (pm create/pm update) does not synchronously re-embed the changed item, so a brand-new item is absent from --mode semantic|hybrid results until the next pm reindex. This keeps writes fast. Control the tradeoff with search.mutation_refresh_policy: cache_only (never refresh on write — fastest), semantic_auto (refresh when implicit Ollama/LanceDB defaults are active), or semantic_configured (refresh only when semantic search is explicitly configured). Keyword mode (--mode keyword, or the auto default on a workspace without a configured semantic provider) always reflects writes immediately because it reads items directly.
Custom Item Types
Custom item types can be defined in settings and by extensions. Runtime type resolution affects create/update validation, list/search/calendar filters, completions, and storage folders.
Prefer pm schema add-type for project definitions:
pm schema add-type Review --folder reviews
pm schema add-type Research --folder research/records
Custom storage folders are portable paths relative to the pm tracker root.
Leading / and Windows separators are canonicalized to that tracker-relative
form. A folder that resolves outside the tracker, uses a Windows drive prefix,
is empty, or contains a NUL byte is rejected. Runtime registry resolution
applies the same guard to existing project settings and extension registrations
before item or history paths are written. This containment contract is tracked
by pm-d30cmk.
Use runtime contracts for exact active types:
pm contracts --json
pm create --help --type Task
Status definitions are authored in .agents/pm/schema/statuses.json; the
schema.statuses projection in settings.json is a deterministic runtime
summary, not a second registry to edit independently. Status ids and aliases are
normalized to underscore tokens in both projections (for example,
in-progress becomes in_progress) while CLI input continues to accept
underscore, hyphen, and space spellings through the resolved status registry.
Custom Runtime Fields
Custom runtime fields add typed flags to pm create/pm update (and optionally list/search/context) and persist their values into item metadata. They are the most powerful customization primitive: a field defined here becomes a first-class --flag with validation, completions, and --help text, with no code changes.
The preferred way to add a field is the CLI —
pm schema add-fieldwrites a well-formed entry for you and validates collisions. Hand-authoringschema/fields.json(below) is equivalent and useful for bulk edits or version-controlled review.
Fields live in .agents/pm/schema/fields.json with the shape { "fields": [RuntimeFieldDefinition...] }:
// .agents/pm/schema/fields.json
{
"fields": [
{
"key": "component", // required: canonical field name (snake_case)
"type": "string", // string | number | boolean | string_array | array | object (default string)
"metadata_key": "component", // item metadata key (defaults to `key`)
"cli_flag": "component", // long flag, used as --component (defaults to key)
"cli_aliases": ["comp"], // extra accepted flag spellings (--comp)
"commands": ["create", "update", "list"], // create | update | update_many | list | search | calendar | context
"description": "Owning component", // shown in --help
"required": false, // required on every mutation when true
"required_on_create": false, // required only on create
"allow_unset": true, // allow clearing via --component "" / --no-component
"required_types": ["Task"], // only required for these item types (subset of required*)
"value_schema": {
// optional recursive semantic constraints
"type": "string",
"enum": ["frontend", "backend", "operations"],
},
},
],
}
RuntimeFieldDefinition properties:
key(required) — canonical field name. Empty/invalid keys are dropped.type—string(default),number,boolean,string_array,array, orobject. Astring_arrayfield is repeatable and union-merged as a collection.arrayandobjectaccept validated JSON containers and retain their JSON shape in item metadata.metadata_key— the item metadata key written/read. Defaults tokey.cli_flag— the long flag name (rendered as--<cli_flag>). Defaults to a normalized form ofkey.cli_aliases— additional flag spellings; duplicates ofcli_flagare ignored.commands— which commands expose the flag. Defaults to["create", "update"].required— value must be present on every accepted mutation.required_on_create— value must be present onpm create(but not on later updates).allow_unset— whentrue(default), the field can be cleared; whenfalse, an explicit unset is rejected.required_types— narrowsrequired/required_on_createto only the listed item types.description— surfaced in--help.value_schema— optional recursive JSON-value validation applied after CLI coercion and before create/update persistence. It supportstype,const,enum,min_length,min_items,format: "date-time", objectproperties/required/additional_properties, arrayitems, and exact-oneone_ofvariants. This lets project-defined fields enforce domain semantics without custom command code.
End-to-end example — add a required-on-create component string field on Task items:
// .agents/pm/schema/fields.json
{
"fields": [
{
"key": "component",
"type": "string",
"cli_flag": "component",
"commands": ["create", "update", "list"],
"description": "Owning component (auth, billing, ...).",
"required_on_create": true,
"required_types": ["Task"],
},
],
}
Object schemas can model discriminated evidence contracts. The following accepts either complete gate proof or an expiring waiver and rejects unknown keys:
{
"key": "gate_evidence",
"type": "object",
"value_schema": {
"type": "object",
"required": ["disposition", "owner"],
"additional_properties": false,
"properties": {
"disposition": {
"enum": ["gate_added", "gate_strengthened", "explicit_waiver"],
},
"owner": { "type": "string", "min_length": 1 },
"gate_id": { "type": "string", "min_length": 1 },
"negative_control": { "type": "string", "min_length": 1 },
"local_checks": {
"type": "array",
"min_items": 1,
"items": { "type": "string", "min_length": 1 },
},
"hosted_checks": {
"type": "array",
"min_items": 1,
"items": { "type": "string", "min_length": 1 },
},
"waiver_reason": { "type": "string", "min_length": 1 },
"waiver_expires_at": { "type": "string", "format": "date-time" },
},
"one_of": [
{
"properties": {
"disposition": { "enum": ["gate_added", "gate_strengthened"] },
},
"required": [
"gate_id",
"negative_control",
"local_checks",
"hosted_checks",
],
},
{
"properties": { "disposition": { "const": "explicit_waiver" } },
"required": ["waiver_reason", "waiver_expires_at"],
},
],
},
}
pm create Task "Fix token refresh" --component auth # stored in item metadata
pm list --component auth # filter by the custom field
Governance note: how item I/O treats metadata keys that are not in the field registry is controlled by schema.unknown_field_policy — allow (default, keeps unknown keys), warn (keeps them but emits a warning), or reject. Set reject to catch field-name typos once your field registry is stable:
pm config project set schema_unknown_field_policy reject # allow | warn | reject
Public Documentation Boundary
Public docs should describe supported user configuration only. Ignored local operations material, unpublished evidence logs, credentials, hostnames, and private service details must stay outside tracked docs and package output.
De docs/CONFIGURATION.md en pm 2026.11.13. Ver en GitHub