> ## 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.

# List sessions

> Cursor-paginated list of sessions on this host, filterable by repository, environment and status. One query across every repository.



## OpenAPI

````yaml /pt/openapi/v3-current/narya.yaml get /v1/sessions
openapi: 3.1.0
info:
  title: Narya Host API
  version: 1.0.0
  description: >-
    The contract between the Narya host and every client. On your own machine,
    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. On your own machine, the host also accepts a
    request with no token from your own operating system user. Once signed-in
    work has claimed the home for an organization, it does so only while this
    machine is signed in to that organization. Such a request gets 401 with
    NRY-0011 on work that reaches your organization beyond the machine. An
    operation that declares another security scheme also accepts that
    credential. Any other 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/sessions:
    get:
      tags:
        - sessions
      summary: List sessions
      description: >-
        Cursor-paginated list of sessions on this host, filterable by
        repository, environment and status. One query across every repository.
      operationId: listSessions
      parameters:
        - $ref: '#/components/parameters/CursorParam'
        - $ref: '#/components/parameters/LimitParam'
        - name: repository
          in: query
          required: false
          description: >-
            Filter to sessions bound to this repository path; on a hosted home,
            relative to its workspace, as each session names it.
          schema:
            type: string
            maxLength: 4096
        - name: environment
          in: query
          required: false
          description: >-
            Filter to sessions bound to this environment. Sessions running on
            the host's own machine are bound to none and match no value of this
            filter; omit it to see them.
          schema:
            $ref: '#/components/schemas/EnvironmentId'
        - name: status
          in: query
          required: false
          description: Filter by session status.
          schema:
            type: string
            enum:
              - idle
              - running
              - waiting
              - archived
        - name: search
          in: query
          required: false
          description: >-
            Filter to sessions whose title contains this text, matched
            case-insensitively. It exists so a client can reach a session by
            name past whatever prefix of the list it has read: a picker that
            filters the newest few hundred rows it holds cannot find a
            conversation from three months ago, and only the host can. Combines
            with the other filters.
          schema:
            type: string
            maxLength: 512
        - name: mine
          in: query
          required: false
          description: >-
            Restrict the listing to the conversations the caller owns. It names
            nobody and takes no name: the subject comes from the verified token
            and from nowhere else, which is what keeps it from being the owner
            parameter this contract deliberately does not have — an owner a
            caller can name is an owner anybody can be. It is not a permission
            and can only ever narrow. Spelled the same way, and meaning the same
            thing, as the `mine` on `searchRecords` and `listRecordFiles`.
            Combines with every other filter here.
          schema:
            type: boolean
      responses:
        '200':
          description: One page of sessions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    CursorParam:
      name: cursor
      in: query
      required: false
      description: >-
        Opaque pagination cursor from a previous page's nextCursor or
        prevCursor.
      schema:
        type: string
        maxLength: 1024
    LimitParam:
      name: limit
      in: query
      required: false
      description: Maximum items per page.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
  schemas:
    EnvironmentId:
      type: string
      minLength: 1
      maxLength: 64
      pattern: ^[A-Za-z0-9][A-Za-z0-9._-]*$
      description: >-
        An environment's identifier, chosen by whoever creates it and unique on
        this host. It travels as one path segment, so its shape is bounded to
        what a path segment carries unchanged, exactly as a monitor's name is: a
        "/" would address no route and a "?" or "#" would end the segment early
        and address something else. One beginning `conversation-` is a hosted
        home's own, and a create naming it is 422.
    SessionPage:
      type: object
      description: One page of sessions.
      required:
        - items
        - limit
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Session'
        limit:
          type: integer
          minimum: 1
        nextCursor:
          type: string
          description: Cursor for the next page; absent on the last page.
        prevCursor:
          type: string
          description: Cursor for the previous page; absent on the first page.
        spend:
          $ref: '#/components/schemas/SessionPageSpend'
      examples:
        - items:
            - id: 6b9f6d2e-1c3a-4f5b-9d7e-2a8c4e6f0b1d
              repository: /Users/dev/repos/midaz
              status: running
              clientKind: inline
              createdAt: '2026-08-08T12:00:00.000Z'
              updatedAt: '2026-08-08T12:34:56.000Z'
          limit: 25
          nextCursor: b3BhcXVlLWN1cnNvcg
          spend:
            filtered:
              inputTokens: 41200000
              outputTokens: 1830000
              costUsd: 301.22
            page:
              inputTokens: 980000
              outputTokens: 44000
              costUsd: 7.15
    Session:
      type: object
      description: >-
        A durable conversation container. Survives the process that created it;
        archiving hides it from active lists but its transcript remains
        queryable and exportable forever.
      required:
        - id
        - repository
        - status
        - clientKind
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
          description: The session's id.
        title:
          type: string
          maxLength: 512
          description: Human-readable title.
        repository:
          type: string
          maxLength: 4096
          description: >-
            Absolute path of the directory the session works in; on a hosted
            home, the path relative to its workspace, which is all a client
            there is shown.

            It is a working-directory LABEL and carries no authority. The
            session's key is `id`, and whose session it is is recorded by the
            host from the identity it authenticated the request as — nothing is
            authorised, looked up or scoped to a tenant by this field. What it
            is read for is what a directory is for: where the tools are
            confined, which project's configuration and instruction files apply,
            and the `repository` filter on listSessions.
        status:
          type: string
          enum:
            - idle
            - running
            - waiting
            - archived
          description: >-
            Lifecycle: running while a turn executes or normal follow-ups chain;
            waiting after interrupt when admitted steering or follow-up inputs
            remain queued; idle only when neither execution nor queued input
            exists; archived accepts no new work.
        provider:
          type: string
          description: The model provider currently in use.
        model:
          type: string
          description: The model currently in use.
        variant:
          type: string
          description: >-
            The preset of request options this session's turns run under — the
            name, not the options it stands for, since what a name means is the
            catalogue's answer and can change while the session's own choice
            cannot. Absent on a session that never chose one, which is every
            session running its model at the model's own defaults.
        style:
          type: string
          maxLength: 64
          description: >-
            The named response style this session's answers are shaped by — the
            name, not the prompt it appends, since what a name means is
            configuration's answer and changes when somebody edits that file
            while the session's own choice does not. Absent on a session that
            never chose one, which is every session answering in narya's own
            voice. Unlike `variant` it survives a model switch: a style is a
            fact about how the conversation reads rather than about the model
            answering it. A client renders this and derives nothing — the names
            that exist are the host's answer (setSessionStyle refuses the rest).
        clientKind:
          $ref: '#/components/schemas/ClientKind'
          description: >-
            Which kind of client opened this session, recorded when it was
            created and never rewritten. Always present: `unknown` for a session
            created before this host recorded the datum, which is the honest
            answer rather than a guessed frontend.
        modelNotice:
          type: string
          maxLength: 1024
          description: >-
            One sentence stating what a client could not otherwise know about
            the model this session opened on or was just moved to, present on
            the responses to createSession and setSessionModel, and on the
            session-created event of a session createSession opened, so a reader
            of the event stream (`narya -p --json`) has it too. Three facts earn
            it. The first is a model narya chose itself rather than being told
            to use: no default_model configured and no model previously chosen,
            so the first provider holding a usable credential answered — or a
            model chosen earlier that the catalogue no longer carries, which was
            passed over. It names the alternatives that also qualified and how
            to choose another, because a choice nobody made has to be stated to
            be a choice at all rather than something that merely happened. The
            second is a level this session will run without: a model reference
            may carry one (see the variant field), and an endpoint that does not
            take the field runs at its own default instead — so a level named at
            creation or at a switch, or configured beside default_model, is said
            here rather than quoted in a picker while every request drops it.
            The third is a default_model no provider lists: it is refused, never
            replaced, so the session opens on no model and this sentence names
            the one configured. A client that renders the live event stream
            rather than the session's transcript has no other road to it.
            Otherwise absent, including for a model that was simply configured,
            chosen or named; absent on getSession and listSessions, which report
            state rather than how it came about.
        usage:
          $ref: '#/components/schemas/TokenUsage'
          description: >-
            What this session has consumed and cost across every turn it has run
            — a running total, not the last turn's figure. Carried by getSession
            and by every row of listSessions, so a client drawing a list of
            sessions can say what each one has cost without asking per row.
            Absent on a session that has never completed a turn, and its costUsd
            is absent whenever any one of that session's turns could not be
            priced: the sum over only the priced turns is smaller than what the
            session actually cost, and a figure that quietly understates money
            is worse than an admitted unknown.
        lastTurnUsage:
          $ref: '#/components/schemas/TokenUsage'
          description: >-
            What the newest round of this session's own conversation sent and
            produced — the LAST turn's figure, not the running total above.
            Carried by getSession only.

            It exists because the two are not interchangeable for the one
            question a reader reopening a long conversation asks: how much room
            is left before this compacts. That answer is the size of the prompt
            last sent, and usage above is a sum over every turn the session ever
            ran, which is a larger number growing without bound and means
            nothing as a fraction of a context window. The live event stream
            carries this figure on turn-finished, so a client watching a session
            has always had it; a client that READ the session had no source for
            it at all and drew no context figure until it spent a turn of its
            own. Restoring a recorded reading rather than deriving one is the
            whole of it — nothing here is computed from the transcript.

            The newest round of the MAIN lane: a delegation's rounds are another
            conversation's prompts, and the figure is about the reader's. Absent
            on a session that has never completed a round, and on one whose
            rounds all ran on delegates. Its costUsd is that one round's price,
            absent when the round could not be priced.
        pendingInputs:
          type: array
          description: >-
            Pending steering and follow-up references in durable operation
            order. Content is intentionally absent; fetch the transcript by
            entryId.
          items:
            $ref: '#/components/schemas/PendingInputReference'
        parentSessionId:
          type: string
          format: uuid
          description: When the session was branched, the session it branched from.
        summary:
          type: string
          maxLength: 4096
          description: >-
            What a model said this conversation tried and concluded. Absent
            until somebody asks for it (summarizeSession) — nothing summarises a
            branch because a rewind moved away from it. A model that answers
            past this length is trimmed to it, with a marker saying so rather
            than a sentence that just stops.
        summaryAt:
          type: string
          format: date-time
          description: >-
            When the summary was taken. Read against updatedAt it answers
            whether the summary predates the last thing that happened here.
        posture:
          $ref: '#/components/schemas/Posture'
          description: >-
            Whether somebody is there to answer a permission prompt raised here.
            Absent on a session nobody ever said either way about, which reads
            by its clientKind (Posture) — the posture is only ever recorded
            because somebody stated it (setSessionPosture), never inferred from
            whether a client happens to be connected.
        warming:
          $ref: '#/components/schemas/Warming'
          description: >-
            Whether this session pays to keep its provider cache alive while
            nobody is using it, and the most it may ever spend doing so. Absent
            on a session nobody ever turned it on for, which is every session
            until somebody does — warming is off unless somebody asked
            (setSessionWarming).
        viewedAt:
          type: string
          format: date-time
          description: >-
            When a reader last had this session in front of them
            (acknowledgeSessionViewed). Read against updatedAt it answers
            whether anything has happened here since somebody looked, which is
            what lets a client mark a background conversation as carrying news —
            and, because the fact is held here rather than in a window, what
            makes two clients agree about it and makes the answer survive a
            relaunch. ABSENT on a session nobody has ever viewed, which is not
            the same as viewed long ago: one has nothing to report, the other
            has everything that ever happened in it.
        lastOutcome:
          type: string
          enum:
            - finished
            - failed
          description: >-
            How the last turn here ended. It is what lets a client that was not
            running at the time tell a conversation that finished from one that
            failed. Absent on a session that has never run a turn. An interrupt
            is not among the values: somebody was there and stopped it
            themselves, so it is not news to bring back to them.
        cacheSpend:
          $ref: '#/components/schemas/CacheSpend'
          description: >-
            What this session's provider prompt cache has cost it, from both
            sides: what letting it expire cost, and what keeping it alive cost.
            Present only on getSession, and only once one of the two has
            happened — a session that has never paid for either carries nothing
            here rather than a pair of zeros.
        environment:
          $ref: '#/components/schemas/SessionEnvironment'
          description: >-
            Where this session's code lives and its commands run, present only
            when that is somewhere other than the host's own machine. Absent is
            the ordinary case and says the session runs here — a developer
            working on their own machine is told nothing, because there is
            nothing to tell. On a hosted home it says instead that the session's
            own container is not made yet: its first turn makes it.
        clone:
          type: string
          description: >-
            The repository cloned into the tree this session works in, as
            `owner/name`: the `clone` it was opened with, or the `repository` of
            the environment it runs on. Absent when nothing was cloned.
        product:
          type: string
          description: >-
            The product tree a product session reads through (createSession's
            `product`); absent on every other session.
        tasks:
          type: array
          description: >-
            The agent's own task list for this session — every item it has
            written, in the order it wrote them, whatever is in the store right
            now.

            It is here rather than behind a listing of its own because the list
            is a stored row and every client already reads the session when it
            attaches, so one optional field costs no round trip and no second
            operation. A change to the list is announced as `task-list`, which
            carries the same whole list for the same reason; this is where a
            client that was not attached at the time gets it.

            Present on getSession only, like `lastTurnUsage` and `cacheSpend`
            and for their reason: a listing that carried it would read every
            row's tasks to draw a page nobody asked for.

            ABSENT MEANS EMPTY, and it cannot mean anything narrower: the store
            holds rows, so a list nobody ever wrote and a list somebody cleared
            are the same read. The live stream is where those two differ — a
            clearing write announces `task-list` carrying an empty array — and a
            read that returned `[]` would be claiming to know which of the two
            it was looking at.
          items:
            $ref: '#/components/schemas/Task'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
          description: >-
            When this session's own ROW last changed. Eight writes stamp it and
            nothing else does: its status, its provider and model, its style,
            its title, its repository, its environment binding, its archival,
            and the status a restart settles it into.

            IT IS NOT A LAST-ACTIVITY CLOCK, and reading it as one is the
            mistake this description exists to prevent. A transcript entry, a
            streamed event, a tool call, a summary and a viewed mark all leave
            it exactly where it was — so a session that is `running` carries THE
            MOMENT ITS TURN WAS CLAIMED, not the moment anything last happened
            in it — and a moment later than that only when one of the other
            seven writes landed during the turn: a rename mid-turn is allowed
            and restamps the row, moving this field forward with nothing having
            happened in the conversation. A caller asking whether a run has gone
            quiet reads the event stream; read off this field, "nothing for N
            minutes" is true of every healthy turn that has been running longer
            than N, and false of a stuck one somebody renamed.

            `SessionAttention.since` carries the same warning for a waiting
            session, and for the same reason.
        owner:
          type: string
          readOnly: true
          description: >-
            Who this session belongs to: the identifier of the person the
            request that created it was authenticated as, inside the
            organisation this host serves. It is who the session is attributed
            to and whose allowance its cache warming is billed against.

            The host writes it from the authenticated identity and there is no
            owner field on any create request — its absence there is the rule
            rather than an omission, because an owner a caller can name is an
            owner anybody can be, and this value is read later by whoever
            decides who pays and who may open a private conversation. A request
            body naming an owner changes nothing.

            `unclaimed` is the one value that is not a person: a store that
            existed before this host recorded owners, on a machine nobody has
            signed in on yet. The first sign-in against such a store replaces it
            everywhere.
        visibility:
          $ref: '#/components/schemas/Visibility'
          description: >-
            Who inside this organisation may read this session. Always present
            on a host that serves this field, unlike `posture` beside it: every
            session HAS a visibility, and the absence of a mark is the
            organisation's rather than a third state, so there is nothing for an
            absent field to mean. A client draws the mark from this value and
            derives nothing — whether a session is held back, and whether this
            reader may see it at all, are the host's answers.
        attention:
          $ref: '#/components/schemas/SessionAttention'
          description: >-
            What this conversation is waiting for, when it is waiting for a
            person. `status` says `waiting` and does not say what for.


            Carried by every row of listSessions and resolved for the whole page
            in one read, which is the point of it: a page of a hundred rows is
            enough to draw an overview of sessions, with no request per row.
            getSession carries the same field for its one session.


            ABSENT WHEN THERE IS NOTHING TO SAY, and absent rather than
            null-shaped: a conversation nobody is being asked about carries no
            field, not an empty object. It is also absent when the caller may
            not see the prompt itself — on a conversation somebody marked
            private, a reader who is not its owner and does not hold the
            read-private permission sees no attention even though the request
            behind it exists, because what this field may say is bounded by the
            same door the ask is behind.
      examples:
        - id: 6b9f6d2e-1c3a-4f5b-9d7e-2a8c4e6f0b1d
          title: Fix flaky ledger test
          repository: /Users/dev/repos/midaz
          status: running
          provider: anthropic
          model: claude-sonnet-4-5
          clientKind: inline
          owner: user_01HZX3Q8N4KDPYV2A7C9M5T6RB
          usage:
            inputTokens: 152000
            outputTokens: 8400
            costUsd: 1.87
          createdAt: '2026-08-08T12:00:00.000Z'
          updatedAt: '2026-08-08T12:34:56.000Z'
    SessionPageSpend:
      type: object
      description: >-
        What this listing costs, so a client drawing an overview of sessions
        does not have to sum the rows it happens to have paged — a figure that
        grows as the reader scrolls and is wrong on every page but the last.

        It is not a second ledger. Every figure here sums the same model rounds
        the consumption report sums, at the same prices, over the conversations
        this request's filters name; where the two surfaces could disagree this
        one defers, because two spend screens that drift is a product people
        stop trusting. They answer an UNPRICED set differently, and
        deliberately: the report serves a floor with `costComplete: false` and
        the count beside it, while this envelope serves no `costUsd` at all with
        the count instead — because the rows in this same body have been
        all-or-nothing since they existed, and a footer reporting a floor over
        rows reporting absence would not equal the sum of the rows above it.

        THE WHOLE ENVELOPE IS ABSENT WHEN THE REQUEST CARRIES `search`,
        `repository` OR `environment`, and never partially present. Those three
        narrow by a name a person chose — a conversation's title, its working
        directory, and an environment identifier whoever created it invented,
        which `listEnvironments` serves to its owner alone — while these figures
        count the conversations somebody marked private under no permission at
        all. Served together they would answer what the read rule refuses:
        `search=Longbow` costing eleven dollars where `search=Longbox` costs
        nothing reads a private title one request at a time,
        `environment=acme-acquisition` reads a name the same way, and
        `repository` and `environment` both attribute that money to a named
        resource. Under those three the absence follows the FILTER and never
        what it matched, so it signals nothing itself — every such request is
        answered without an envelope, whether it matched a private conversation,
        a readable one or nothing. Spend grouped by repository lives on the
        consumption report, which is the operation built for it and which puts a
        private conversation's money in one opaque group rather than under a
        name. `status` and `mine` are not content and keep the envelope: a run's
        state is not something a person wrote, and `mine` narrows to the
        reader's own work.

        Separately, and for a different reason, a ledger this host cannot sum is
        answered with the rows and no envelope. That is one bit, the same under
        every filter and for every caller, and it names nothing.
      required:
        - filtered
        - page
      properties:
        filtered:
          allOf:
            - $ref: '#/components/schemas/SpendTotal'
          description: >-
            What the whole filtered set cost — every conversation these filters
            match, not just the ones on this page. This is the number an
            overview of sessions shows for "spending".

            It counts the conversations somebody marked private too, under no
            permission at all, because money inside an organisation is the
            organisation's and a total that changed with the reader would not be
            a total. Nothing but money crosses that mark: this figure names no
            conversation, no repository, no environment and no person — and
            where a filter would turn it into a probe for one of those names,
            the whole envelope is withheld rather than narrowed, which is the
            `search`, `repository` and `environment` rule stated above.
        page:
          allOf:
            - $ref: '#/components/schemas/SpendTotal'
          description: >-
            What the conversations in `items` cost — summed over exactly the
            rows served here, so it reconciles with them to the cent.
        private:
          allOf:
            - $ref: '#/components/schemas/SpendTotal'
          description: >-
            What this page matched and did not serve: the conversations the
            filters selected and the read rule withheld, because somebody marked
            them private and this caller is neither their owner nor named on a
            share. The first page also carries every product session of another
            member, which no page lists. Absent when this page withheld nothing,
            which is every page of a home where nobody has marked anything.

            It exists so the parts still add up to the whole. Without it,
            `filtered` would exceed the pages by however much private work cost,
            with nothing in the answer saying where the difference went — and a
            report whose parts do not sum to its whole is the shape this product
            refuses. The money is disclosed; the names are not.
    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.
    ClientKind:
      type: string
      enum:
        - inline
        - tui
        - oneshot
        - api
        - unknown
      x-enum-varnames:
        - ClientKindInline
        - ClientKindTui
        - ClientKindOneshot
        - ClientKindApi
        - ClientKindUnknown
      description: >-
        Which kind of client opened a session — the coarse classification a
        client filters and mixes on when it lists sessions it did not itself
        open. inline — the terminal client (`narya`). tui — the full-screen
        client (`narya --tui`). oneshot — a one-shot command (`narya -p`). api —
        a caller of this contract that named no kind; every narya frontend names
        itself, so this is what speaking the contract directly is. unknown —
        never recorded, which is every session created before this host carried
        the datum; a read answers `unknown` rather than picking a kind, and a
        create request naming it records nothing. It also answers who attends a
        session that recorded no posture: inline and tui have a person behind
        them, so a permission prompt is asked; oneshot, api and unknown have
        nobody, so a prompt is refused with its reason. In a repository nobody
        has trusted, an api or unknown session's turn is refused naming the
        trust gesture; a oneshot session runs there under the operator's rules
        without the repository's own files, whoever created it.
    TokenUsage:
      type: object
      description: >-
        Token consumption totals and their cost. costUsd is priced from the
        catalogue's published rates for the model that ran, and is absent
        whenever no rate is known for it — a model configured by hand against a
        private endpoint, say. Absent means unknown; a false zero would mean
        free. On a session total it is absent unless every turn in the session
        could be priced.
      required:
        - inputTokens
        - outputTokens
      properties:
        inputTokens:
          type: integer
          minimum: 0
          description: Input tokens consumed.
        outputTokens:
          type: integer
          minimum: 0
          description: Output tokens produced.
        cacheReadTokens:
          type: integer
          minimum: 0
          description: Tokens served from the provider's prompt cache.
        cacheWriteTokens:
          type: integer
          minimum: 0
          description: Tokens written to the provider's prompt cache.
        costUsd:
          type: number
          minimum: 0
          description: Total cost in US dollars.
      examples:
        - inputTokens: 152000
          outputTokens: 8400
          cacheReadTokens: 120000
          cacheWriteTokens: 9000
          costUsd: 1.87
    PendingInputReference:
      type: object
      description: >-
        A content-free durable queued-input reference. Message content remains
        only in the transcript.
      required:
        - entryId
        - turnId
        - kind
        - sequence
        - enqueuedAt
      properties:
        entryId:
          type: string
          format: uuid
        turnId:
          type: string
          format: uuid
        kind:
          type: string
          enum:
            - steer
            - follow-up
        sequence:
          type: integer
          format: int64
          minimum: 1
        enqueuedAt:
          type: string
          format: date-time
      examples:
        - entryId: 4d8e2f6a-0b1c-4d3e-9f5a-7b9c1d3e5f0a
          turnId: 4d8e2f6a-0b1c-4d3e-9f5a-7b9c1d3e5f0a
          kind: follow-up
          sequence: 7
          enqueuedAt: '2026-08-10T15:04:05.000Z'
    Posture:
      type: string
      enum:
        - attended
        - detached
      x-enum-varnames:
        - PostureAttended
        - PostureDetached
      description: >-
        Whether somebody is there to answer a permission prompt raised in a
        session. attended — a prompt is shown and waited on. detached — nobody
        is watching, so a prompt is refused and recorded with its reason rather
        than blocking the turn forever on a question nothing can answer. A
        session that recorded neither is attended when a terminal client
        (clientKind inline or tui) opened it, or it is a product session, and
        detached otherwise.

        Orthogonal to clientKind, and deliberately not a value of it: clientKind
        is stamped once at creation and never rewritten, whereas a session
        BECOMES detached when somebody says so. It is a recorded fact with
        exactly one transition (setSessionPosture) and is never inferred from
        the presence or absence of a connection — a client that crashed, a lid
        that closed and a pipe that dropped all leave it exactly as it was.
    Warming:
      type: object
      description: >-
        One session's standing instruction about its provider cache while nobody
        is using it: whether to pay to hold the cached prefix past the five
        minutes a provider keeps it for free, and the lifetime dollars this
        session may spend doing so.

        Both fields travel together because they are one decision. Turning
        warming on without saying what it may cost is not a decision anybody
        makes, and there is no default narya could supply that is not a guess
        about somebody else's bill.

        The ceiling is a LIFETIME figure for this session, not a daily one: a
        session is a bounded thing with an end, and "this conversation may cost
        me at most fifty cents to keep warm" is the sentence a person means. A
        second, separate ceiling bounds the whole store's spending per UTC day
        and is operator configuration rather than part of any session — either
        one being reached stops warming rather than warning about it.
      required:
        - enabled
        - ceilingUsd
      properties:
        enabled:
          type: boolean
          description: >-
            Whether this session is warmed. False with a ceiling recorded is a
            session somebody turned off, which keeps the number they chose for
            the day they turn it back on.
        ceilingUsd:
          type: number
          format: double
          minimum: 0
          description: >-
            The most this session may ever spend on warming, in US dollars,
            counted across its whole life. Reaching it stops warming this
            session and nothing else; every other session keeps its own
            allowance. Zero is a legitimate value and means the same as off.
      examples:
        - enabled: true
          ceilingUsd: 0.5
    CacheSpend:
      type: object
      description: >-
        What this session's provider prompt cache has cost it, read from both
        sides of one decision: what letting the cached prefix expire cost, and
        what refusing to let it expire cost.

        The two numbers are reported together because they are the same decision
        seen from either end. Somebody weighing whether to turn warming on for
        this session is comparing exactly these figures, and either one alone
        answers half the question.

        The headline is the re-billed prompt: rounds that paid full input rates
        to send again a prefix the provider had been holding, because the
        session sat idle past the cache's lifetime. A duplicated cache WRITE is
        a real event too and is deliberately not reported here — it is an
        internal metric, not what a person reads on a bill.

        Absent on a session where neither has ever happened, which is every
        session until one of them does. Never a zero-filled object standing in
        for "nothing yet".
      required:
        - rebilledRounds
        - rebilledTokens
        - rebilledUsd
        - warmingUsd
      properties:
        rebilledRounds:
          type: integer
          minimum: 0
          description: >-
            How many rounds paid to re-send an expired prefix. Zero here with a
            warming figure present is a session that has been kept warm and has
            therefore never paid for its own idleness — which is warming
            working.
        rebilledTokens:
          type: integer
          minimum: 0
          description: >-
            The prefix re-sent across those rounds: the part of each round's
            cache write that had genuinely been cached before, never the whole
            write. A resumed conversation also writes the message that ended the
            wait, and that content would have been written with the prefix alive
            too — charging it to the expiry would blame idleness for the weight
            of the message that ended it.
        rebilledUsd:
          type: number
          format: double
          minimum: 0
          description: >-
            What that expiry cost, in US dollars: for each round, the difference
            between what its prompt was billed and what the same prompt would
            have cost with the prefix still cached.

            It DECOMPOSES money already reported in `usage.costUsd` rather than
            adding to it. A re-billed round's whole price is in the session
            total like every other round's, and this says how much of that price
            the expiry was; adding the two together counts the same dollars
            twice.
        rebilledUsdComplete:
          type: boolean
          description: >-
            Absent means true. Present and false makes `rebilledUsd` a FLOOR —
            "at least this much" — because at least one re-billed round ran on a
            model this host cannot price, contributing a real expiry and no
            dollars. The same honesty `costComplete` carries on a usage report,
            for the same reason: a total that quietly skipped the rounds it
            could not price is a number a reader will believe.
        warmingUsd:
          type: number
          format: double
          minimum: 0
          description: >-
            What this session has spent keeping its cache alive, in US dollars,
            counted across its whole life and against the ceiling in
            `warming.ceilingUsd`. Reservations are included: a warm that was
            paid for and whose outcome was never established is money that may
            already be gone, and leaving it out would let it be spent twice.
      examples:
        - rebilledRounds: 4
          rebilledTokens: 18240
          rebilledUsd: 0.0312
          warmingUsd: 0.045
    SessionEnvironment:
      type: object
      description: >-
        A session's binding to an environment, as a client draws it beside the
        session's identity. The id and the state, and deliberately nothing else:
        everything a person acts on is here, which keeps a page of twenty-five
        sessions from carrying twenty-five setup transcripts.
      required:
        - id
        - state
      properties:
        id:
          $ref: '#/components/schemas/EnvironmentId'
        state:
          $ref: '#/components/schemas/EnvironmentState'
    Task:
      type: object
      description: >-
        One item of the agent's task list: what the work is, and where it
        stands.

        Two fields and no more. There is no id, because the list is replaced
        whole on every write and position is the only order there is — an
        identifier would be a second thing two surfaces could disagree about.
        There is no priority, because nothing renders or orders by one.
      required:
        - content
        - status
      properties:
        content:
          type: string
          description: What the task is, in the agent's own words.
        status:
          type: string
          enum:
            - pending
            - in_progress
            - completed
            - cancelled
          description: >-
            Where the item stands. `cancelled` counts toward the total and not
            toward what is done — an abandoned step is still a step the list
            once held. The engine does not enforce "exactly one in_progress":
            that is guidance in the tool's own description, and a list holding
            two is drawn honestly rather than refused.
          x-enum-varnames:
            - TaskStatusPending
            - TaskStatusInProgress
            - TaskStatusCompleted
            - TaskStatusCancelled
      examples:
        - content: Reproduce the flaky ledger test
          status: completed
    Visibility:
      type: string
      enum:
        - organisation
        - private
      x-enum-varnames:
        - VisibilityOrganisation
        - VisibilityPrivate
      description: >-
        Who inside this organisation may read a session. organisation — every
        member may, which is the default and what every session is until
        somebody marks it. private — its owner, plus a member holding
        `sessions:read-private`, plus whoever a share of it names.

        Two values and no third. There is no "unset": the absence of a mark IS
        the organisation's, so "nobody said" and "the team may read it" are one
        fact rather than two, and nothing in this product has a use for a
        visibility somebody would have to define.
    SessionAttention:
      type: object
      description: One prompt standing on a conversation, waiting for a person.
      required:
        - kind
        - askId
        - since
      properties:
        kind:
          type: string
          enum:
            - permission
            - question
          description: >-
            Which of the two things a person answers is holding this
            conversation: a permission request for an action the agent wants to
            take, or a question the agent put to the user.


            A refinement round is deliberately not one of the values. It blocks
            no tool call, so a conversation standing on one is not waiting for
            anybody and must not be drawn as though a turn were parked.
        askId:
          type: string
          format: uuid
          description: >-
            The prompt itself — the permission id or the question id, according
            to `kind` — so a client acts on it directly instead of paging a
            listing to find which of several it just drew.
        since:
          type: string
          format: date-time
          description: >-
            When the PROMPT was raised, which is not when the session last
            changed. "Waiting forty minutes" has to mean the person has been
            kept waiting forty minutes; read off `updatedAt` it would mean only
            that nothing has happened here for forty minutes, which is true of
            an idle conversation nobody is blocked on.


            Where several prompts stand on one conversation this is the OLDEST,
            for the same reason: the question an overview of sessions asks is
            how long somebody has been waiting, not when the most recent thing
            appeared.
    SpendTotal:
      type: object
      description: >-
        Tokens consumed and money billed over a set of conversations.

        costUsd is ALL OR NOTHING, and `unpricedRounds` is why. A model this
        host cannot price records no cost at all, so a sum that skipped those
        rounds would be smaller than the real bill while looking exactly like it
        — and a zero would read as free. So the money is served only when every
        round in the set was priced, and otherwise it is absent with the count
        of the rounds that were not. One unpriceable round in a thousand and a
        thousand out of a thousand are both unknown; only the count says which.
        EXACTLY ONE OF THE TWO IS ALWAYS PRESENT, and the `oneOf` below says so
        where a reader generating a client can see it: neither field is optional
        in the sense of "may be missing" — a set is priced or it is counted.

        The money is a DOUBLE here while the per-session figure on a session row
        (`TokenUsage`) is a single. That is deliberate rather than inconsistent:
        `costUsd` on this schema sums a whole organisation and grows for the
        life of a home, and a single's step passes one cent at about 131,072
        dollars, which would break the reconciliation this envelope promises. A
        session's own total is one conversation's spend and stays orders of
        magnitude below that, so it keeps the type every client already reads.
      required:
        - inputTokens
        - outputTokens
      properties:
        inputTokens:
          type: integer
          minimum: 0
          description: Input tokens consumed across the set.
        outputTokens:
          type: integer
          minimum: 0
          description: Output tokens produced across the set.
        cacheReadTokens:
          type: integer
          minimum: 0
          description: Tokens served from the provider's prompt cache across the set.
        cacheWriteTokens:
          type: integer
          minimum: 0
          description: Tokens written to the provider's prompt cache across the set.
        costUsd:
          type: number
          format: double
          minimum: 0
          description: >-
            Total cost in US dollars. Absent when `unpricedRounds` is present:
            unknown, never a zero standing in for free.

            Double precision, because this figure sums a whole organisation and
            never stops growing; the per-session figure on a session row is a
            single and says why in this schema's own description.
        unpricedRounds:
          type: integer
          minimum: 1
          description: >-
            How many model rounds in this set recorded no cost at all, which is
            why `costUsd` is absent. Absent itself when every round was priced,
            which is when `costUsd` is the whole of it.
      oneOf:
        - required:
            - costUsd
        - required:
            - unpricedRounds
      examples:
        - inputTokens: 41200000
          outputTokens: 1830000
          cacheReadTokens: 22000000
          cacheWriteTokens: 1400000
          costUsd: 301.22
    EnvironmentState:
      type: string
      enum:
        - ready
        - degraded
        - gone
        - unusable
      x-enum-varnames:
        - EnvironmentStateReady
        - EnvironmentStateDegraded
        - EnvironmentStateGone
        - EnvironmentStateUnusable
      description: >-
        Whether an environment is answering, and — when it is not — whether
        waiting is the right move. The distinction is the whole point: a session
        waiting on an unreachable environment looks identical to a slow one
        otherwise, and what the person does next depends entirely on which it
        is. ready — answering; work proceeds. degraded — not answering right now
        and expected back: a restart or a dropped connection, which the host
        retries so the next call may simply succeed, or a container stopped
        outside narya, which the next turn starts. gone — destroyed, its name
        taken by another environment since, or the runtime no longer has it. A
        hosted home also removes a container nothing has used for 15 minutes and
        keeps its disks, the workspace and its restore points, and so does a
        person's own home for a conversation's own `conversation-<sessionId>`;
        it reads gone until a turn needs it. A session bound to it is then given
        a new container on its next turn, over the disks the old one left when
        those are still there, and is told which; on a person's own home a named
        environment that is gone needs a new one. unusable — its setup did not
        succeed; `setup` says what happened.
  responses:
    BadRequest:
      description: Malformed request — invalid parameter, cursor, or JSON body.
      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 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 — except a request with no
        token on a laptop's socket from the OS user the kernel names as the
        host's own, which is that machine's user.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.