> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Narrow a fact to one checkout

> Narrows one of this owner's global facts to the single repository it should keep applying in, and does it ON THE SPOT: nothing is asked, because narrowing what narya may do is never the dangerous direction and making cleanup expensive is how a ledger nobody prunes gets built. The log is the undo.

IT IS ALSO HOW A WIDENING IS UNDONE, in one gesture rather than two: the general row narrows back, the row it superseded stays exactly where it is, and the ledger keeps saying where the fact was first learned. Rolling the row back instead takes the fact away entirely, which is the delete.

It answers 200 with the row as the narrowing left it rather than 204, so `narya learned demote --json` has something to print.



## OpenAPI

````yaml /en/openapi/v3-current/narya.yaml post /v1/refinements/{refinementId}/demote
openapi: 3.1.0
info:
  title: Narya Host API
  version: 1.0.0
  description: >-
    The contract between the Narya host and every client. One long-lived host
    serves this API over a Unix socket in your Narya home. The terminal client
    and the one-shot command that Lerian ships drive this API, and a client you
    write drives the same one.


    Requests authenticate with a bearer token issued by the identity provider
    the host is configured with. An operation that declares another security
    scheme also accepts that credential. A request without a valid credential
    gets 401 with NRY-0011. A caller whose role lacks the permission an
    operation needs gets 403 with NRY-0028.


    Submitting a message returns 202 with a turn id. Everything the turn
    produces streams over GET /v1/events as server-sent events with a typed
    envelope, and a stream resumes from a Last-Event-ID header.


    One error envelope: code, title and message. Codes are NRY- followed by four
    digits. Cursors are opaque.
servers: []
security:
  - bearerAuth: []
tags:
  - name: host
    description: The host process itself — version, uptime, mode, store.
  - name: sessions
    description: Durable conversation containers. Archive, never destroy.
  - name: messages
    description: Submitting work into a session and interrupting it.
  - name: events
    description: The server-sent event stream every client consumes.
  - name: lanes
    description: Parallel tracks inside a session — main, subagent, side.
  - name: agents
    description: Named recipes — instructions, tools, model, policy. Read-only in v1.
  - name: ladder
    description: >-
      What a person can type: the skills and command files in force for one
      repository, and expanding one into text. Five origins merged, nearest
      winning a name, the repository's own rungs gated on trust.
  - name: permissions
    description: Pending permission asks, decisions, and the decision audit.
  - name: intercom
    description: Sessions on one machine finding and messaging each other.
  - name: packages
    description: The one thing a user installs — resources, Go code, or both.
  - name: extensions
    description: >-
      Host-side extensions and the operations each exposes over the wire. This
      is the generic lane a host extension uses to serve its own client half (a
      TUI component, a web panel) or any API-only consumer, without adding
      routes to this contract.
  - name: workflows
    description: Deterministic multi-agent orchestration runs.
  - name: providers
    description: Model suppliers, their auth state, and the model catalogue.
  - name: monitors
    description: >-
      Long-running watchers a session keeps beside its conversation — a test
      runner in watch mode, a build, a log being followed. Started by the model
      or by the person, always listed, always killable.
  - name: records
    description: The queryable local record of everything that happened.
  - name: environments
    description: >-
      Where a session's code lives and its commands run — this machine, or a
      container narya operates. A session that names none runs here.
  - name: schedules
    description: >-
      Work the host's own clock starts with nobody present — a repository, a
      prompt, a rule and what one fire may spend. Cancel, never destroy.
  - name: sharing
    description: >-
      Publishing a session from a developer's own home to the organisation's,
      and what is held back before a byte leaves the machine.
  - name: refinements
    description: >-
      What this owner has taught narya and allowed it to keep — distilled facts,
      and the skills, agents and commands the model wrote for itself. Propose,
      read, consent, roll back. Nothing here fires until a person answers.
  - name: platform
    description: >-
      Calls Lerian's control plane made to a home it hosts, as this home
      recorded them.
paths:
  /v1/refinements/{refinementId}/demote:
    parameters:
      - $ref: '#/components/parameters/RefinementIdParam'
    post:
      tags:
        - refinements
      summary: Narrow a fact to one checkout
      description: >-
        Narrows one of this owner's global facts to the single repository it
        should keep applying in, and does it ON THE SPOT: nothing is asked,
        because narrowing what narya may do is never the dangerous direction and
        making cleanup expensive is how a ledger nobody prunes gets built. The
        log is the undo.


        IT IS ALSO HOW A WIDENING IS UNDONE, in one gesture rather than two: the
        general row narrows back, the row it superseded stays exactly where it
        is, and the ledger keeps saying where the fact was first learned.
        Rolling the row back instead takes the fact away entirely, which is the
        delete.


        It answers 200 with the row as the narrowing left it rather than 204, so
        `narya learned demote --json` has something to print.
      operationId: demoteRefinement
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefinementDemotion'
      responses:
        '200':
          description: The refinement, as the narrowing left it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Refinement'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/RequestBodyTooLarge'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    RefinementIdParam:
      name: refinementId
      in: path
      required: true
      description: The refinement's id.
      schema:
        type: string
        format: uuid
  schemas:
    RefinementDemotion:
      type: object
      description: >-
        Where a narrowing leaves the fact. The ledger keeps exactly two scopes,
        so a demotion has nothing to say except which checkout the fact keeps
        applying in — and it has to say that, because a narrowing to nowhere
        would leave the row where it was with nothing reported.
      required:
        - repository
      properties:
        repository:
          type: string
          minLength: 1
          description: >-
            The checkout the fact keeps applying in, canonicalised by the host
            the same way a trust answer's path is.

            `""` IS NOT A NARROWING TO THE OWNER'S GLOBAL SCOPE by another
            spelling, and the host refuses it with a 422 rather than reading it
            as one — as it refuses anything else that canonicalises to nothing.
            Required and non-empty are one statement here: the only thing a
            demotion can be missing is its destination, and a row silently left
            where it was is the write nobody asked for.
      examples:
        - repository: /Users/dev/repos/ledger
    Refinement:
      type: object
      description: >-
        One thing the harness learned and a person allowed: a distilled memory,
        or a skill, agent or command definition the model authored.

        The owner is never something a caller names.

        `script` and `events` are present on getRefinement and absent from the
        listing. A page carrying every authored program's source is a page
        nobody can read; the hash is on both, which is what "what was consented
        to is what runs" is checked against.
      required:
        - id
        - kind
        - scope
        - owner
        - title
        - body
        - state
        - origin
        - createdAt
        - hits
      properties:
        id:
          type: string
          format: uuid
        kind:
          $ref: '#/components/schemas/RefinementKind'
        scope:
          $ref: '#/components/schemas/RefinementScope'
        repository:
          type: string
          description: The canonical checkout path, absent at the owner's global scope.
        owner:
          type: string
          description: Whose learning this is.
        name:
          type: string
          description: >-
            The ladder identity of the three definition kinds, absent on a
            memory.
          maxLength: 128
        title:
          type: string
          maxLength: 200
        body:
          type: string
          description: The fact's text, or the definition's prompt or description.
        script:
          type: string
          description: >-
            The program the model wrote, IN FULL — never a digest and never a
            path. A person cannot consent to what they were not shown, and what
            they consented to is what the confined runtime later runs, byte for
            byte. Present on getRefinement only.
        scriptSha256:
          type: string
          description: >-
            The hash of that program, on the listing as well as the read. It is
            what the runtime checks the text against before running it.
          pattern: ^[0-9a-f]{64}$
        state:
          $ref: '#/components/schemas/RefinementState'
        origin:
          $ref: '#/components/schemas/RefinementOrigin'
        originSessionId:
          type: string
          description: >-
            The conversation this was learned from. It may name a session that
            has since been purged, which is what `unverifiable` reads from.
        originEntryId:
          type: string
          description: The turn this was learned in.
        originActor:
          type: string
          description: Who was acting when this came in over the contract.
        citations:
          type: array
          description: The turns this stands on, in the order they were written.
          items:
            $ref: '#/components/schemas/RefinementCitation'
        events:
          type: array
          description: >-
            Everything that has happened to this refinement, newest first.
            Present on getRefinement only.
          items:
            $ref: '#/components/schemas/RefinementEventEntry'
        createdAt:
          type: string
          format: date-time
        activatedAt:
          type: string
          format: date-time
          description: When a person consented. Absent while pending.
        lastUsedAt:
          type: string
          format: date-time
          description: >-
            When this last fired into a prompt. It is what eviction is ordered
            by — a fact that fires often is a fact that earned its place — and
            it is deliberately never rewound by a rollback.
        hits:
          type: integer
          format: int32
          minimum: 0
          description: How many turns this has fired into.
        supersededBy:
          type: string
          format: uuid
          description: The row that replaced this one, if any.
      examples:
        - id: 7d1e3a5c-9b2f-4e6a-8c0d-2f4a6c8e0b2d
          kind: memory
          scope: repository
          repository: /Users/dana/repos/midaz
          owner: local
          title: Migrations and the schema document are one change
          body: >-
            A new migration file without the matching block in schema.sql fails
            two tests the moment it lands.
          state: active
          origin: loop
          originSessionId: 6b9f6d2e-1c3a-4f5b-9d7e-2a8c4e6f0b1d
          originEntryId: 8a1d3f5b-7c9e-4a2b-8d6f-0c2e4a6b8d0f
          citations:
            - sessionId: 6b9f6d2e-1c3a-4f5b-9d7e-2a8c4e6f0b1d
              entryId: 8a1d3f5b-7c9e-4a2b-8d6f-0c2e4a6b8d0f
          createdAt: '2026-09-07T09:12:00.000Z'
          activatedAt: '2026-09-07T09:12:00.000Z'
          hits: 4
    RefinementKind:
      type: string
      enum:
        - memory
        - skill
        - agent
        - command
      x-enum-varnames:
        - RefinementKindMemory
        - RefinementKindSkill
        - RefinementKindAgent
        - RefinementKindCommand
      description: >-
        What a refinement IS. Four values and no more — one vocabulary is what
        makes "everything this repository taught narya" one question.
    RefinementScope:
      type: string
      enum:
        - repository
        - global
      x-enum-varnames:
        - RefinementScopeRepository
        - RefinementScopeGlobal
      description: >-
        How far a refinement reaches. `repository` is keyed on the canonical
        checkout path, the same key a trust answer uses; `global` is the
        owner's.

        There is no `session` here and the absence is a decision: a
        session-scoped fact lives exactly as long as the conversation that gave
        it, so it is never a stored row at all.
    RefinementState:
      type: string
      enum:
        - pending
        - active
        - rolled-back
        - unverifiable
      x-enum-varnames:
        - RefinementStatePending
        - RefinementStateActive
        - RefinementStateRolledBack
        - RefinementStateUnverifiable
      description: >-
        Where a refinement stands. `pending` is proposed and nothing else —
        never rendered, never executed, never on a ladder. `active` is consented
        and firing. `rolled-back` is kept for the record.

        `unverifiable` is the one that surprises: such a fact is STILL FIRING
        and says so. Every session it cited has been purged, which is a person
        deleting a transcript rather than a person retracting a belief.
    RefinementOrigin:
      type: string
      enum:
        - loop
        - api
        - import
      x-enum-varnames:
        - RefinementOriginLoop
        - RefinementOriginApi
        - RefinementOriginImport
      description: >-
        Which road a refinement came in on: narya's own review of a
        conversation, a client teaching it through this contract, or an import.
        Both roads land pending and pass the same consent.
    RefinementCitation:
      type: object
      description: >-
        One pointer into the transcript that justifies a refinement. The
        admitted user entry's id IS the turn id, so citing the turns needs no
        second identifier.

        THE POINTER MAY DANGLE, and that is the design: nothing cascades from a
        purged session to a fact learned in it, because a person deleting one
        noisy conversation must not silently lose weeks of learning. A fact
        whose evidence is gone becomes `unverifiable` and keeps firing.
      required:
        - sessionId
        - entryId
      properties:
        sessionId:
          type: string
          format: uuid
        entryId:
          type: string
          format: uuid
        note:
          type: string
          description: What the review said about this turn.
          maxLength: 2000
      examples:
        - sessionId: 6b9f6d2e-1c3a-4f5b-9d7e-2a8c4e6f0b1d
          entryId: 8a1d3f5b-7c9e-4a2b-8d6f-0c2e4a6b8d0f
          note: the third attempt at the same import cycle
    RefinementEventEntry:
      type: object
      description: >-
        One thing that happened to one refinement. The log is APPEND-ONLY:
        nothing in it is ever updated or deleted, because "who taught narya
        this, and who took it away" has to be answerable from the log alone.
      required:
        - id
        - action
        - at
      properties:
        id:
          type: string
          format: uuid
        action:
          $ref: '#/components/schemas/RefinementAction'
        actor:
          type: string
          description: Who acted. Empty when narya's own loop did.
        at:
          type: string
          format: date-time
        causedByEventId:
          type: string
          format: uuid
          description: >-
            The event this one inverts, absent when it inverts nothing. It is
            what makes a rollback of a rollback readable as a redo.
      examples:
        - id: 5c7e9a1b-3d5f-4a7c-9e1b-3d5f7a9c1e3b
          action: activated
          actor: local
          at: '2026-09-07T09:12:00.000Z'
    Error:
      type: object
      description: >-
        The single error envelope every operation returns. Codes are NRY-
        followed by four digits and are catalogued in the top-level
        x-error-catalog extension.
      required:
        - code
        - title
        - message
      properties:
        code:
          type: string
          pattern: ^NRY-[0-9]{4}$
          description: Machine-readable error code from the NRY catalogue.
        title:
          type: string
          maxLength: 256
          description: Short human-readable summary of the error class.
        message:
          type: string
          maxLength: 4096
          description: Specific, actionable description of what went wrong.
        fields:
          type: object
          description: Per-field validation problems.
          additionalProperties:
            type: string
      examples:
        - code: NRY-0002
          title: Session not found
          message: >-
            No session with id 6b9f6d2e-1c3a-4f5b-9d7e-2a8c4e6f0b1d exists on
            this host.
    RefinementAction:
      type: string
      enum:
        - created
        - activated
        - updated
        - promoted
        - demoted
        - rolled-back
        - deleted
      x-enum-varnames:
        - RefinementActionCreated
        - RefinementActionActivated
        - RefinementActionUpdated
        - RefinementActionPromoted
        - RefinementActionDemoted
        - RefinementActionRolledBack
        - RefinementActionDeleted
      description: >-
        What happened to a refinement. There is no `restored`: restoring what
        somebody undid is a rollback OF a rollback, so undo and redo are one
        verb aimed at different events and the log nests instead of branching.
  responses:
    BadRequest:
      description: Malformed request — invalid parameter, cursor, or JSON body.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: The addressed resource does not exist on this host.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Conflict:
      description: >-
        The resource's current state rejects the request — archived session,
        busy lane, slot conflict, failed build, already-final state.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RequestBodyTooLarge:
      description: >-
        The request body weighs more than this operation accepts (NRY-0024). The
        host holds one ceiling per operation and refuses at the door, in front
        of every handler: a body whose declared Content-Length is past the
        ceiling is refused before a byte of it is read, and a body that declares
        no length is read only as far as the ceiling and refused there. Nothing
        was read past that point and nothing was written.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    UnprocessableEntity:
      description: The request was well-formed but failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalServerError:
      description: The host failed — including a store that refuses writes (NRY-0012).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        Enforced on every transport, with no exempt operation. A person's
        request — over the default local unix socket exactly as over a TCP
        listener — must carry a JWT issued by the configured identity provider,
        which the host verifies itself against that issuer's key set: signature,
        issuer, expiry, and the person and organisation it names. Requests
        without a valid one receive 401 NRY-0011. The socket's file permissions
        are transport and are not an authorisation.

````