Skip to content

Manifest Reference

Every field in the apiVersion: felix/v1 manifest schema. Source of truth: packages/harness/src/felix/manifests/schema.py, plus the compile-time rules in manifests/governance.py and the binders in manifests/builder.py.

Every model is Pydantic v2 with extra="forbid", so an unknown key is a hard parse error — not a warning. Where a field has a default, the default is what you get if you omit it.

Top-level shape

apiVersion: felix/v1 # default; only this exact value is accepted
kind: Agent # default; only this exact value is accepted
metadata: { ... } # required
spec: { ... } # defaults to a minimal react agent

Validate one without starting the stack:

Terminal window
uv run felix validate-manifest manifests/quick.yaml
uv run felix validate-manifest manifests/governed.yaml -e production # applies production rules

metadata

Field Type Default Notes
name string, 1–128 chars, ^[a-zA-Z0-9._-]+$ required The manifest id, the OpenAI model value, the audit manifest_id, and an object-store key segment. No slashes or whitespace, so it cannot escape its key prefix.
version string "1.0.0" Free-form. Distinct from the tenant store’s monotonic version number.
description string "" Surfaced on GET /v1/models and the agent card.
tags string[] [] Free-form labels.

spec.pattern

spec:
pattern: react # default

Built-ins: react, deep, reflect, plan_execute (single-agent) and router, parallel, groupchat (multi-agent). The registry is open — register_pattern(name, builder) adds more, and nothing in core enumerates the list. See Patterns.

Multi-agent patterns require sub_agents and reject peers, containers, queues, sandboxes and browser_tools; a sub-agent’s own leaf manifest declares those instead.

spec.model

spec:
model:
id: claude-sonnet
temperature: 0
max_tokens: 4096
thinking_level: medium
fallbacks: [claude-haiku]
Field Type Default Notes
id string | null null Logical route name. Null falls back to FELIX_DEFAULT_MODEL_ID (claude-sonnet).
temperature float 0.0
max_tokens int | null null Provider default when unset.
region string | null null
cache bool false Provider prompt caching.
thinking_budget int | null, 12864000 null Extended-thinking token budget.
thinking_level off minimal low medium high xhigh max | null null When set, overrides thinking_budget via the level map.
fallbacks string[] [] Tried in order on a provider error; increments the felix_model_switch counter.
confidence_escalation object disabled See below.
price map[string, float] {} USD per 1M tokens, overriding the built-in table for cost attribution.

Logical ids resolve through DEFAULT_MODEL_ROUTES, overridable with FELIX_MODEL_ROUTES: claude-opus, claude-sonnet, claude-haiku, claude-fable, gpt-4.1, gpt-4.1-mini, llama-3-pro, llama-3-fast. The suffixed claude-sonnet-4 and claude-haiku-4 are retained as legacy aliases onto the current model in the same tier; prefer the unsuffixed ids. Providers are anthropic, openai, ollama, workers_ai, groq, together, deepseek, cerebras, fireworks, openrouter, xai, mistral and google, plus any registered through register_model_provider. Note that limits.max_cost_usd is refused at compile when the resolved model has no known rates — supply them with spec.model.price. See Model client.

confidence_escalation retries with a stronger model when the answer looks hedged:

Field Type Default
enabled bool false
escalate_to string ""
low_confidence_markers string[] ["i am not sure", "i don't know", "i cannot answer", "unclear", "uncertain", "no information"]
min_response_chars int ≥ 0 40

A client may override the model mid-session with POST /chat {"model": "..."}, but only against an allowlist built from spec.model.id, spec.model.fallbacks, FELIX_DEFAULT_MODEL_ID and the route table. Anything else is 400 model_not_allowlisted:<id>.

spec.system_prompt

spec:
system_prompt:
inline: |
You are a support agent. Cite the knowledge base.
soul: false
base: ""
Field Type Default Notes
inline string "" Written in the manifest.
soul bool false Prepend the tenant’s shared “soul” prompt.
base string "" Shared preamble.
files string[] [] Keys loaded after base/inline and appended.
system_md string | null null Key loaded as a full replacement (SYSTEM.md semantics).
append_system_md string | null null Key appended after the composed prompt.

Parts are joined with "\n\n---\n\n" in the order soul → base → inline, then file-sourced parts. Empty parts are dropped.

spec.prompts

Named user-message templates, expanded with $1 / $@ / ${1:-default} and invoked by passing template and template_args on a chat request.

spec:
prompts:
- name: triage
body: "Triage this ticket: $1. Priority hint: ${2:-normal}"
Field Type Default Notes
name string, 1–64 chars required
body string ""
file string | null null Object-store / workspace key; used when body is empty.

spec.tools

A list of built-in tool names.

spec:
tools: [calculator, read_file, write_file, search_files, list_dir]
Tool Args Notes
calculator {expression} Safe expression evaluator.
list_dir {path} Max 500 entries.
read_file {path, offset, limit} Max 512 KB; flags binary files.
write_file {path, content, append} Max 512 KB.
search_files {query, path, regex, max_hits} ≤ 50 hits; skips files over 256 KB.
list_skills / activate_skill / deactivate_skill Bound to the real catalog when spec.skills is set.

The four workspace tools resolve every path under FELIX_WORKSPACE_ROOT, rejecting absolute paths and any traversal that escapes the root. With the variable unset they fail with workspace_root is not configured (set FELIX_WORKSPACE_ROOT).

Patterns contribute their own tools: plan_create / plan_update_step / plan_get (deep), and remember_procedure when procedural memory is on.

spec.skills

spec:
skills:
- name: calculator-help
Field Type Default
name string required
version string | null null
description string | null null

A skill is a SKILL.md with YAML frontmatter under skills/<name>/. The frontmatter can fold extra tools, MCP servers and peers into any manifest that lists it; the body is appended to the system prompt under an ## Active Skills header. Activation is per-tenant and restriction-only — a tenant overlay can disable declared skills but never enable undeclared ones.

spec.mcp_servers

Each entry becomes one tool per remote tool, namespaced {name}__{tool}.

spec:
mcp_servers:
- name: notion
url: https://mcp.notion.example.com
transport: sse
auth: secret:NOTION_TOKEN
Field Type Default Notes
name string required Tool namespace.
url string "" Required for http / sse.
command string "" Required for stdio.
args string[] []
cwd string ""
env map[string,string] {}
auth string "" Literal token or secret:NAME.
transport http | sse | stdio sse

Outbound URLs pass an SSRF guard; internal hostnames are blocked unless allowlisted.

stdio is disabled by default

transport: stdio spawns a subprocess from manifest-supplied argv — arbitrary code execution as the API process. It is refused unless the operator names the exact command in FELIX_MCP_STDIO_ALLOWED_COMMANDS (comma-separated; empty, the default, disables stdio entirely). The check runs on manifest write (400), at compile, and at spawn. The child inherits only PATH, HOME, LANG, LC_ALL, TZ plus declared keys, and loader variables such as LD_PRELOAD, PYTHONPATH, NODE_OPTIONS and BASH_ENV are rejected.

spec.peers

Each entry becomes a peer__{name} tool that calls a remote Felix over A2A.

spec:
peers:
- name: billing
url: https://billing.example.com
auth: secret:BILLING_TOKEN

name and url are required; auth accepts a literal or secret:NAME. Hop depth is bounded by limits.max_peer_hops.

spec.containers

An HTTPS container gateway, one tool per entry.

Field Type Default Notes
name string required
description string ""
gateway_url string required
image string required
container_tool_name string ""
timeout_ms int | null null
auth string "" Literal or secret:NAME.
args_schema JSON Schema | null null
fatal bool false When true, a failure aborts the tool batch.

spec.queues

Asynchronous work handed to a separate consumer over a Valkey/Redis list. The result lands back on the session later, so the run can continue without blocking.

spec:
queues:
- name: reindex
queue_binding: felix-jobs
deadline_ms: 600000
Field Type Default Notes
name string required
description string ""
queue_binding string required The Redis list key the executor pushes to.
deadline_ms int | null null
args_schema JSON Schema | null null
fatal bool false

The consumer writes the result back with POST /internal/sessions/{session_id}/events, which requires the X-Felix-Consumer-Secret header.

spec.sandboxes

Container-backed code execution (the sandbox extra, Docker).

Field Type Default
name string required
description string ""
binding string required
sandbox_tool_name string ""
timeout_ms int | null null
path_prefix string ""
args_schema JSON Schema | null null
fatal bool false

spec.browser_tools

Browser automation through the browser extra (Playwright). Install it explicitly — it is never in the default image.

Field Type Default
name string required
description string ""
binding string required
op content | links | snapshot | screenshot | pdf | json content
timeout_ms int | null null
path_prefix string ""
args_schema JSON Schema | null null
fatal bool false

spec.client_tools

Tools the browser or CLI client executes, not the server. This is how the web clients run local shell commands and open local files.

spec:
client_tools:
- name: local_shell
description: Run a shell command in the user's local environment.
args_schema:
type: object
properties:
command: { type: string }
cwd: { type: string }
required: [command]
additionalProperties: false
timeout_seconds: 300
Field Type Default Notes
name string required
description string ""
args_schema JSON Schema | null null
timeout_seconds float, 0 < x ≤ 3600 null → 120 s
fatal bool false

When the model calls one, the harness emits a tool_request frame and blocks until the client answers with POST /chat/tool_result. A client that never answers stalls the run until the timeout, which returns [error/timeout] client tool timed out as the tool result. See the streaming contract.

spec.sub_agents and spec.aggregator_prompt

sub_agents is a list of manifest names, required by the multi-agent patterns and resolved at build time. aggregator_prompt (string, "") tells parallel and groupchat how to merge their results.

spec.max_turns

spec:
max_turns: 4 # default; ceiling 100

spec.memory

spec:
memory:
checkpointer: postgres # default; `none` also built in
store: pgvector # default
Field Type Default Notes
checkpointer postgres | none, or a registered name postgres Where the session log lives. none keeps no session state, so every turn starts from the messages it was given. Add your own with register_checkpointer.
store pgvector | memory | none (aliases agentcore, vectorize accepted but inert) pgvector Durable-fact vector store.
capture object disabled Extract durable facts from a turn.
consolidate object disabled Inert today — do not rely on it.

capture:

Field Type Default
enabled bool false
model string "claude-haiku"
max_facts int, 1–20 5
min_chars int ≥ 0 80
verify bool false

verify adds a second pass that hands the proposed facts back to model along with the excerpt and keeps only what the excerpt actually supports. It doubles the extraction calls, which is why it is off by default — turn it on where stored memory is acted on rather than merely recalled.

The pass can only remove. Its verdict selects from the proposals; it never supplies a memory of its own, and it cannot alter a kept memory’s kind, topic_key or importance. That matters because the excerpt it reads is untrusted transcript, and topic_key drives supersession — a verdict that could set one could delete an unrelated stored fact.

It fails open on anything it cannot read, on the grounds that a broken verifier is not evidence the facts were wrong:

Verdict Result
A subset of the proposals Just that subset is stored
[] — a well-formed empty array Nothing is stored. The one unambiguous way to reject everything
The call errors The unverified set is kept
Unparseable, or every item unusable The unverified set is kept
Valid items that match no proposal The unverified set is kept — the verifier rewrote rather than chose

Content is matched case- and whitespace-insensitively but not punctuation-insensitively, so a verifier that echoes an item back with a trailing full stop counts as having rewritten it.

Extraction runs on model, not on the turn’s model — it is a small, mechanical job on every completed turn, and billing it to a frontier model roughly doubles the cost of having memory at all. If that model cannot be built, extraction falls back to the turn’s model rather than dropping the fact.

Facts land in memory_vectors (pgvector, HNSW) with one of four kinds: fact, event, instruction, task. The extractor asks the model to classify each fact, and anything outside that set is silently rewritten to fact rather than rejected — so a prompt that invents a kind degrades quietly instead of failing. Semantic recall needs the embeddings extra.

Procedural memory is a separate subsystem under spec.procedural_memory, not a kind in this table.

spec.session

spec:
session:
strategy: compacting
reserve_tokens: 16384
keep_recent_tokens: 20000
context_window_tokens: 128000
Field Type Default Notes
strategy string "full_replay" full_replay, windowed:N, summarizing:N, semantic:N (needs embeddings), compacting.
compaction_enabled bool true
reserve_tokens int ≥ 0 16384 Headroom kept for the reply.
keep_recent_tokens int ≥ 0 20000 Never compacted.
context_window_tokens int ≥ 1024 128000
steering_mode all | one-at-a-time all
follow_up_mode all | one-at-a-time all
branch_summary bool true Summarize an abandoned branch on rewind.
compact_after_turn bool false

The session log is append-only and lives outside the context window; the strategy decides what the model actually sees. See Persistence.

spec.auth

spec:
auth:
inbound:
allow_anonymous: false
required_scopes: [chat:write]
outbound:
providers: []
Field Type Default Notes
inbound.schemes string[] [] Enforced. The scheme the caller authenticated with (api_key, or the JWT verifier scheme) must be listed; jwt is an umbrella for every configured JWT scheme. Mismatch → 403.
inbound.required_scopes string[] [] Enforced. Missing scopes → 403.
inbound.allow_anonymous bool false Enforced. Anonymous callers get 401 unless true.
outbound.providers string[] [] Enforced at compile. The resolved provider for the primary model and every fallback must be listed, or the build fails — not the first model call.

spec.a2a and spec.observability

spec:
observability:
trace: true # default
Field Type Default Notes
a2a.publish bool true Opt-out. false withholds the agent from /.well-known/agent-card.json. The default is true because the field was never read before, so every agent was already advertised; a false default would have 404’d every existing manifest.
a2a.capabilities[] {id, description, input_schema_ref} [] Merged into the agent card, alongside spec.skills.
observability.trace bool true
observability.metrics[] string[] [] Empty = every counter. Non-empty is an allowlist: unlisted counter names are dropped before the series exists, which also bounds the Prometheus cardinality that tenant-supplied manifest ids and remote MCP tool names would otherwise create.

spec.execution

spec:
execution:
mode: durable # default: transient
tools: sequential # default
Field Type Default Notes
mode transient | durable transient durable runs the invocation as a fiber.
resume_token_ttl_seconds int | null nullFELIX_HIBERNATE_AFTER_SECONDS (300)
tools parallel | sequential sequential Tool-call execution within a batch.

A durable POST /chat returns 202 with {status, resume_token, fiber_id, expires_at, thread_id}; poll GET /chat/runs/{resume_token}. Fibers checkpoint in Postgres and are resumed by the worker’s fiber_scheduler cron. Setting FELIX_DURABILITY=temporal additionally starts a Temporal workflow on the felix-fibers task queue, falling back to the fiber scheduler if that fails.

POST /chat/stream ignores execution.mode: durable and always runs inline. Use POST /chat plus polling when you need durability.

spec.tools_retrieval

Selects a relevant subset of tools per turn instead of sending the whole catalogue.

Field Type Default
enabled bool false
top_k int ≥ 1 20
model string "bge-base-en-v1.5"

spec.artifacts

Spills oversized tool output to the object store and shows the model a stub.

Field Type Default
enabled bool false
threshold_chars int ≥ 1 8000
preview_chars int ≥ 1 200
default_window_chars int ≥ 1 4000
max_window_chars int ≥ 1 16000

Output over the threshold is written to artifacts/{tenant}/{manifest}/{uuid}.txt and replaced with a preview plus an [artifact:<id> key=<key> …] marker. Keys are tenant-scoped.

spec.reflect

Field Type Default
verifier_model string ""
threshold float 0–1 0.7
max_iterations int 1–5 2
criteria string ""

spec.plan_execute

Field Type Default
planner_model string ""
executor_model string ""
max_subtasks int 1–20 8
replan_on_failure bool true
max_replans int 0–5 2
executor_recursion_limit int 1–20 6
planner_few_shots int 0–10 3

spec.procedural_memory

Field Type Default
enabled bool false
top_k int ≥ 1 3
embedding_model string "bge-base-en-v1.5"

Adds a remember_procedure tool and recalls stored how-tos as procedural memory rows.

spec.policies

Scope-gated tool access.

spec:
policies:
- id: notion-writes
required_scopes: [notion:write]
tools: ["notion__create_page"]
Field Type Default
id string required
description string ""
required_scopes string[] []
tools string[] (glob, e.g. github__*) []

A denial emits a policy_deny audit event with status: denied.

spec.limits

spec:
limits:
max_tool_calls: 200
max_wall_clock_seconds: 3600
Field Type Default Absolute ceiling
max_tool_calls int | null null 500
max_wall_clock_seconds float | null null 3600
max_peer_hops int | null null 5
max_input_tokens int | null null 1,000,000
max_output_tokens int | null null 100,000
max_cost_usd float | null null 1,000.00
precount bool false Count input tokens before the call rather than after.

max_cost_usd is a per-run spend ceiling, accumulated from the model catalog’s prices as tokens are metered. It fails closed: a model the catalog cannot price is a pricing gap that mis-enforces the cap, so supply spec.model.price for any model you route to that the built-in table does not cover.

spec.recursion_limit (int | null, ceiling 50) and spec.max_turns (ceiling 100) are capped the same way. A breach denies the call like any other governance layer: the tool result becomes a deny string, felix_tool_calls{status="denied"} increments, and the run records a policy_deny audit row. There is no distinct limit_exceeded event type.

spec.guardrails

spec:
guardrails:
providers: [pii]
block_on_match: true
targets: [input, output, final_response]
judges:
- name: helpfulness
criteria: "Answers the question without inventing facts."
threshold: 0.7
Field Type Default
providers list of pii []
block_on_match bool false
targets list of input | output | final_response ["input","output"]
judges JudgeRule[] []

JudgeRule: name (required), criteria (required), threshold (float 0–1, default 0.7), model (string, ""), target_tools (string[], []), final_response (bool, false).

providers is a closed set, like targets beside it: a typo (PII, pii-redaction) is a compile error rather than a manifest that applies no wrapper while reporting guardrails enabled.

The pii provider needs the pii extra (Presidio) plus a spaCy English model. Without both — which is the case on the lean image — it degrades to three regexes (email, US SSN, card-like digit runs), announced at WARNING with a felix_control_degraded counter, not silently. Guardrail blocks and below-threshold judge scores deny through the same path as every other layer — a policy_deny audit row and felix_tool_calls{status="denied"} — not under event types of their own.

spec.content_screening

Screens tool output for prompt injection before the model sees it.

Field Type Default Notes
enabled bool false
model string "" Empty = marker-based only; set an id to add an LLM injection score.
tools string[] [] Empty = all tools.
on_flag quarantine | block quarantine Applies to output that is flagged and to output that could not be screened.

Inbound user turns are screened on /chat, /v1 and /a2a as well.

A screener that cannot run has not cleared anything. If the model is unreachable — missing key, expired credential, 429, provider outage — or returns a score that cannot be parsed, the result is unavailable, not clean: on_flag: block denies (inbound: 503), quarantine replaces the content, and a felix_control_unavailable counter is emitted. This matters most on the tool-output path, which is what screens MCP, A2A, browser, and sandbox content.

spec.command_screening

Pattern rules over shell-style command arguments.

spec:
command_screening:
enabled: true
include_defaults: true
rules:
- pattern: "rm -rf /"
decision: deny
reason: "Destructive"
- pattern: "git push"
decision: require_approval
Field Type Default Notes
enabled bool false
include_defaults bool true Ship-with floor rules.
rules CommandRule[] []
target_tools string[] []
approval_ttl_seconds int, 0 < x ≤ 86400 300 How long a require_approval rule waits before failing closed.

CommandRule: pattern (1–256 chars, required), decision (allow | deny | require_approval, required), reason (string | null).

require_approval creates a real approval request and blocks on it, bounded by approval_ttl_seconds so a run cannot wait forever on an approver who never comes.

spec.anomaly

Thresholds for the worker’s anomaly scan. Read from the manifest — findings carry the values that produced them.

Field Type Default Notes
enabled bool true false disables the scan for this manifest.
min_volume int ≥ 1 10 Below this call volume the window is not scored.
baseline_factor float ≥ 1 3.0 Multiple of the baseline that counts as an anomaly.
min_rate float 0–1 0.2 Still unimplemented — deliberately, since its intended semantics are not recoverable from the code.

spec.approvals

Human-in-the-loop gates on named tools.

spec:
approvals:
- id: workspace-write
description: Confirm writes to the workspace
tools: [write_file]
ttl_seconds: 600
one_shot: true
bind_principal: true
Field Type Default Notes
id string required
description string "" Shown to the approver.
tools string[] []
ttl_seconds int > 0 | null null How long the run waits for a decision.
one_shot bool false The grant is consumed once. Enforced with a conditional update, so two concurrent identical calls cannot both spend it.
bind_principal bool false The grant is bound to the approving principal; it will not authorize another user’s identical call.
allow_unattended bool false Permit the call with no human present.
when_args string[] [] Gate only the calls carrying all of these arguments, non-empty. Empty gates every call.

when_args exists because a tool can be harmless in one shape and a privileged operation in another. remember is ordinary capture until it carries a topic_key, at which point it retires whatever else holds that key — the same outcome forget is gated for. Gating the whole tool would put an approval in front of every memory write:

spec:
approvals:
- id: memory-retopic
description: Confirm replacing the current value of a remembered topic
tools: [remember]
when_args: [topic_key]

A call that does not carry every named argument reaches the tool untouched — no approval, no approval_required event, no added latency.

A gated call creates a pending row keyed by a SHA-256 signature of its arguments, emits approval_required, and blocks on the decision. Operators answer with POST /approvals/{id}/decide (approved / denied, optional note, optional edited_args which replace the call arguments). It fails closed: no request context, no store, or a store error all deny.

spec.governance

Opt-in framework mapping. These are compile-time requirements, not a certification.

spec:
governance:
frameworks: [soc2]
forbid_plaintext_secrets: true
pin_compile: true
retention_days: 365
Field Type Default Notes
frameworks list of soc2 | eu_ai_act [] Empty is a no-op, so local manifests stay valid.
risk_tier limited | high limited EU AI Act deployer hint.
transparency_notice bool false Prepends an “you are talking to an AI agent” notice.
forbid_plaintext_secrets bool false Also forced on when FELIX_ENVIRONMENT=production.
pin_compile bool false Freezes the manifest version for a thread; drift → 409.
retention_days int 1–3650 | null null

Setting any framework forces forbid_plaintext_secrets and pin_compile to true, and then:

soc2 requires observability.trace: true, anomaly.enabled: true, at least one of policies / approvals / limits, at least one of auth.inbound.schemes / required_scopes, and auth.inbound.allow_anonymous: false outside development.

eu_ai_act requires governance.transparency_notice: true and at least one of content_screening.enabled or guardrails targeting input. With risk_tier: high it additionally requires a non-empty approvals list in which no rule sets allow_unattended: true.

Verify in CI with felix validate-manifest <file> -e production. See deploy/GOVERNANCE.md.

Secret references

Anywhere a credential is accepted (mcp_servers[].auth, peers[].auth, containers[].auth) you may write a literal, secret:NAME, or {"secret": "NAME"}. Names resolve at compile through FELIX_SECRETS_BACKEND (env | file | aws | gcp) and are masked out of tool output. An unresolvable name fails the request with 503. With forbid_plaintext_secrets on, a literal is a compile error.

Examples

A minimal agent:

apiVersion: felix/v1
kind: Agent
metadata:
name: quick
description: Fast general-purpose assistant.
spec:
pattern: react
model:
id: claude-sonnet
temperature: 0
system_prompt:
inline: You are a concise, accurate assistant.
tools: [calculator]
max_turns: 8

A governed workspace agent with client tools, approvals and durable execution — this is the bundled cowork manifest the web clients drive:

apiVersion: felix/v1
kind: Agent
metadata:
name: cowork
version: 1.0.0
description: Felix workspace agent — goals, files, client tools, and approvals.
tags: [react, workspace, durable]
spec:
pattern: react
model:
temperature: 0
system_prompt:
inline: |
You are Felix, a workspace agent.
Prefer small, reversible changes. Summarize what you did when finished.
tools:
- calculator
- list_dir
- read_file
- write_file
- search_files
client_tools:
- name: local_shell
description: Run a shell command in the user's local environment.
args_schema:
type: object
properties:
command: { type: string }
cwd: { type: string }
required: [command]
additionalProperties: false
timeout_seconds: 300
skills:
- name: calculator-help
approvals:
- id: workspace-write
description: Confirm writes to the workspace
tools: [write_file]
ttl_seconds: 600
- id: client-shell
description: Confirm local shell commands
tools: [local_shell]
ttl_seconds: 600
limits:
max_tool_calls: 200
max_wall_clock_seconds: 3600
max_turns: 40
execution:
mode: durable
session:
strategy: compacting
reserve_tokens: 16384
keep_recent_tokens: 20000
context_window_tokens: 128000
memory:
checkpointer: postgres
store: pgvector
auth:
inbound:
allow_anonymous: true
observability:
trace: true

Fields that parse but do nothing yet

Do not build on these — they validate, but no code path consumes them: spec.memory.consolidate.* and spec.anomaly.min_rate.

memory.checkpointer used to be on this list. It now selects the session store for real, and the three names that could never work here — agentcore, do (Durable Objects, which this stack does not run) and sqlite — are a validation error rather than a silent alias for postgres.