Skip to content

Proposal: front-end-owned machine-readable integration contract #852

Open

Description

@lapy

Context

PR #848 proposed putting CLI and server transport details into model specs. The review correctly identified that as the wrong ownership boundary, and the PR is closed. This issue proposes the integration outcome without reviving that schema direction.

Problem

External control planes such as my own project (llamacppStudio) need a reliable, machine-readable contact surface to install packages, start audio.cpp, discover loaded-model capabilities, construct valid requests, consume streaming events, and interpret results. Today an integration has to hardcode routes and field conventions, infer support from model-family names or option names, or analyze source code and documentation. Those approaches drift as the server and model set evolve.

A model spec alone must not become that contact surface: it cannot safely own HTTP paths, JSON/multipart mappings, CLI flags, server.json fields, stream envelopes, or response layouts. Those belong to the front-end that implements them.

Proposal

Provide a versioned, machine-readable integration contract generated by each front-end from its own implementation. In particular:

  • The server owns and publishes its lifecycle/configuration schema, operation identifiers, routes, request encodings and fields, response forms, errors, and streaming events.
  • The CLI owns and publishes its invocation/configuration surface independently.
  • Existing v1 model specs remain model-facing: family metadata, packages, tasks, modes, capabilities, logical inputs/outputs when stabilized, options, and dependencies.
  • The server composes its API contract with the selected model's v1 metadata for model discovery. A client can learn which logical tasks and options a model supports and which server-owned operations can serve them, without a model spec copying an HTTP route or wire mapping.

The exact transport and endpoint are deliberately open for discussion. It could be a small introspection endpoint, an OpenAPI-like document, a --describe --json command, or a shared internal descriptor that powers all of those. The important constraint is one authoritative server/CLI definition, rather than parallel metadata.

Why this matters to integrators

Studio is a useful concrete consumer. It manages model catalogs and packages, starts audio.cpp as a subprocess or service, and presents a unified UI across model families. To integrate safely it needs to answer, from supported data rather than heuristics:

  1. What server/CLI configuration is accepted to start a model?
  2. Which installed or loaded models expose which logical tasks, modes, capabilities, inputs, and options?
  3. Which server operation should be used for each supported task?
  4. How must a request be encoded, including audio artifacts and streaming?
  5. What response, error, and stream-event shapes should the client handle?

Without this contract, every integration hardcodes family-specific rules or duplicates source-code knowledge. That makes new models harder to adopt, causes silent incompatibilities after API changes, and makes third-party front ends less useful to audio.cpp users.

Suggested incremental path

  1. Document and test the v1 model-spec ownership boundary; do not add schema 2.
  2. Introduce a server-local canonical operation descriptor, with no behavior change.
  3. Expose a versioned server contract from that descriptor.
  4. Compose per-model discovery from the server contract and existing v1 model metadata.
  5. Add a separate CLI description surface if it cannot share the server descriptor.
  6. Evolve v1 package or logical-I/O metadata only through small, separately reviewed proposals.

Non-goals

  • No HTTP, request-body, response, streaming, CLI, or server-config fields in model specs.
  • No schema 2 proposal.
  • No requirement that every model immediately provide richer logical metadata. Unknown remains preferable to inferred support.

I would appreciate guidance on the preferred shape and hosting location for this front-end-owned contract before implementation begins.

Activity

  1. 0xShug0 commented on Oct 13, 2026

    @0xShug0
    Owner

    @lapy Thanks! This is generally useful, but please hold for now. A few more refactors are underway. Once they're done, I'll check how this fits into the codebase and ping you.

  2. lapy commented on Oct 13, 2026

    @lapy
    ContributorAuthor

    Hi @0xShug0, of course! No rush :)
    Thanks for considering it and for the great work you’re doing with Audio.cpp.

  3. supomarlo commented on Oct 23, 2026

    @supomarlo
    Contributor

    This direction would help downstream clients like mine.

    I maintain a desktop client for audio.cpp. It does not bundle the server; users point
    it at their own copy, so versions vary, including third-party builds.

    A smaller related need: exposing the server version over HTTP (e.g. in GET /health)
    would be useful. Model support and interfaces differ between versions, and the client
    needs to know which version it is talking to.

    --version is the only programmatic source today, and it is not reliable for me. Some
    servers were started outside the app, so I cannot reliably query the binary that
    launched them. Some third-party builds do not produce a parseable version string. In
    both cases, version detection becomes cumbersome and fragile.

  4. chenhz01 commented on Nov 2, 2026

    @chenhz01

    The hold makes sense mid-refactor — one small thing for whenever this lands: supomarlo's /health version point composes naturally with the contract idea, since a published contract already needs a version field to be meaningful. To make the shape testable before any routes get frozen, here's a validator for the server-owned surface:

    """Validate a front-end-owned integration contract (issue #852 direction).
    Usage: python contract_lint.py contract.json
    Checks the fields the server should publish: semver, routes with request/
    response encodings, errors, stream events, plus an independent CLI surface.
    """
    import json, sys
    
    REQUIRED_SERVER = ["version", "routes", "errors", "stream_events"]
    REQUIRED_ROUTE = ["path", "method", "request", "response"]
    VERBS = ("GET", "POST", "PUT", "DELETE", "PATCH")
    
    def err(msgs, path, msg):
        msgs.append("%s: %s" % (path, msg))
    
    def check_routes(spec, msgs):
        if not spec.get("routes"):
            err(msgs, "routes", "empty - clients would hardcode again")
        for i, r in enumerate(spec.get("routes", [])):
            for k in REQUIRED_ROUTE:
                if k not in r:
                    err(msgs, "routes[%d]" % i, "missing '%s'" % k)
            if r.get("method") not in VERBS:
                err(msgs, "routes[%d].method" % i, "unknown verb")
    
    def check_version(spec, msgs):
        v = str(spec.get("version", ""))
        parts = v.split(".")
        if len(parts) != 3 or not all(p.isdigit() for p in parts):
            err(msgs, "version", "'%s' is not semver" % (v or "missing"))
    
    def main():
        if len(sys.argv) != 2:
            sys.exit("usage: contract_lint.py <contract.json>")
        spec = json.load(open(sys.argv[1], encoding="utf-8"))
        msgs = []
        check_version(spec, msgs)
        check_routes(spec, msgs)
        for k in REQUIRED_SERVER:
            if k not in spec:
                err(msgs, "root", "missing '%s'" % k)
        cli = spec.get("cli")
        if not isinstance(cli, dict) or not cli.get("invocations"):
            err(msgs, "cli", "CLI surface missing - owned independently of server")
        if msgs:
            print("CONTRACT INVALID (%d problems)" % len(msgs))
            for m in msgs:
                print(" -", m)
            return 1
        print("CONTRACT OK: %s - %d routes, %d stream events, CLI surface present"
              % (spec.get("version"), len(spec["routes"]), len(spec["stream_events"])))
        return 0
    
    if __name__ == "__main__":
        sys.exit(main())

    It checks the fields the thread agrees the server should own — semver, routes with request/response encodings, errors, stream events — plus an independently-owned CLI block. Run it against a draft contract.json during the refactors and whatever survives is the schema worth publishing. Happy to draft the schema spec and this linter as a PR whenever you're ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions