MINARA

Learned preferences

GET / POST /v1/preferences — list, inspect, approve, reject, and deprecate preferences mined from the user's conversation.

The preference-evolution system mines durable user preferences from conversation (e.g. "I never trade meme coins", "reply concisely", "prefer on-chain data over K-line") and stages them through a state machine before they take effect.

M1 shipped read-only endpoints; M2 added the approve / reject / deprecate mutations + the total field on the list endpoint; M3 added the undo endpoint and tool-level enforcement of hard_constraint rows.

M4 adds the bridge: every transition into active now writes the preference through to the relevant downstream stores — user_tags for hard constraints that name a valid 11-dimension tag, a memories mirror for every personal_style row (so the style hint is FTS- indexed), scenario-classifier boosts for behavioral_preference rows targeting a scenario, and a methodology priority multiplier for rows targeting a methodology id. Transitions out of active (deprecate / reject) reverse each write. Bridge writes are best-effort and never block a state transition; failures surface through logs with learning/preference-bridge. See the Self-improving system feature page for the full write-path diagram.

Every preference has three classifying axes:

  • kind — one of personal_style, behavioral_preference, hard_constraint.
  • dimension — one of risk, chain, asset_class, timing, methodology, style, constraint.
  • state — one of proposed, active, deprecated, rejected.

See the project plan at ~/.claude/plans/autoclaw-agent-agent-lazy-lollipop.md for the full lifecycle.


GET /v1/preferences

List preferences with optional filters.

MethodGET
Path/v1/preferences
AuthAuthorization: Bearer <token> required when GATEWAY_AUTH_TOKEN is set
Querystate, dimension, kind, limit (default 100, max 500)
Categorylearning

Response 200:

{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "pref_a1b2c3d4e5f6",
        "user_id": "default",
        "dimension": "asset_class",
        "kind": "hard_constraint",
        "statement": "Never trade meme coins",
        "structured": { "operator": "exclude", "subjects": ["meme_coin"], "scope": ["buy", "swap"] },
        "evidence": null,
        "source": "manual",
        "state": "active",
        "confidence": 0.65,
        "times_observed": 4,
        "times_violated": 0,
        "dedup_key": "asset_class:hard_constraint:never trade meme coins",
        "bridges_user_tag": null,
        "approved_at": "2026-04-15T08:00:00Z",
        "last_ask_at": null,
        "created_at": "2026-04-13T10:00:00Z",
        "updated_at": "2026-04-15T08:00:00Z"
      }
    ],
    "count": 1,
    "total": 1
  }
}

count is the number of rows in this page (≤ limit); total is the number of rows matching the filters across the full table. When count == limit && total > limit, narrow the filters or page through with smaller limit values to see the rest.

Response 422: returned when state, dimension, kind, or limit is invalid.


GET /v1/preferences/:id

Fetch a single preference by id.

MethodGET
Path/v1/preferences/:id
AuthAuthorization: Bearer <token> required when GATEWAY_AUTH_TOKEN is set
Categorylearning

Response 200: same row shape as the items[] element above.

Response 404: returned when the id does not exist.


POST /v1/preferences/:id/approve

Promote a proposed row to active. Setting approved_at if not already set. Equivalent to the REPL /preferences approve command and minara preferences approve <id>.

MethodPOST
Path/v1/preferences/:id/approve
AuthAuthorization: Bearer <token> required when GATEWAY_AUTH_TOKEN is set
Bodyoptional { "reason": "..." } (audit trail, ≤ 500 chars)
Categorylearning

Response 200: the updated preference row in the standard envelope.

Response 404: id not found.

Response 409: invalid transition (e.g. row is already rejected) OR another writer beat this caller (CAS lost — re-GET to see the current state).


POST /v1/preferences/:id/reject

Promote proposed (or active, when retiring an existing preference) → rejected. Same shape as approve above; rejected is terminal — the row is preserved as an audit trail but never re-surfaced.

MethodPOST
Path/v1/preferences/:id/reject
AuthAuthorization: Bearer <token> required when GATEWAY_AUTH_TOKEN is set
Bodyoptional { "reason": "..." }
Categorylearning

POST /v1/preferences/:id/deprecate

Promote activedeprecated. The preference stops being injected into the system prompt but the row is preserved (so the similarity gate can still detect re-proposals as duplicates and skip re-asking). Use reject if you want the row to never come back as a similar-statement match.

MethodPOST
Path/v1/preferences/:id/deprecate
AuthAuthorization: Bearer <token> required when GATEWAY_AUTH_TOKEN is set
Bodyoptional { "reason": "..." }
Categorylearning

POST /v1/preferences/:id/undo

Back out a strong-signal auto-activated hard_constraint within the configured undo window (default 24h, see PREFERENCE_HARD_UNDO_WINDOW_HOURS). The row transitions to deprecated, tool-level enforcement stops immediately, and the audit trail (auto_activated_at timestamp, original statement) is preserved. M3-introduced.

Use deprecate instead when either (a) the preference was approved manually (so auto_activated_at is null) or (b) the window has expired.

MethodPOST
Path/v1/preferences/:id/undo
AuthAuthorization: Bearer <token> required when GATEWAY_AUTH_TOKEN is set
Bodyoptional { "reason": "..." } (≤ 500 chars)
Categorylearning

Response 200 — row deprecated.

Response 404 — id not found.

Response 410 Gone — row is auto-activated but the undo window expired. Use deprecate instead.

Response 413 — body > 4 KB.

Response 422 — row was approved manually (no auto_activated_at). Use deprecate instead.

Response 409 — CAS lost (another writer changed the row first) or invalid structural transition.

On this page