Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
dc78339
test: require native HIP manifest target
alexhegit Oct 27, 2026
9ab812c
feat: admit native HIP Flint bundles
alexhegit Oct 27, 2026
9930771
test: require native HIP launch contract
alexhegit Oct 27, 2026
769f037
feat: launch native HIP Flint runtime
alexhegit Oct 27, 2026
b349bf8
test: scaffold local model verification coverage
alexhegit Oct 27, 2026
2c561fa
feat: verify and serve local native checkpoints
alexhegit Oct 27, 2026
9376877
test: require Linux native HIP preflight
alexhegit Oct 27, 2026
9399462
feat: preflight Linux native HIP hosts
alexhegit Oct 27, 2026
76e12d4
feat: add pinned Linux HIP Flint manifest
alexhegit Oct 27, 2026
c0b1a62
docs: generalize native platform messaging
alexhegit Oct 27, 2026
d5bc2ca
fix: size HIP host RAM preflight for native loading
alexhegit Oct 27, 2026
d32e5a5
test: exercise native HIP managed lifecycle
alexhegit Oct 27, 2026
9949f8c
build: stage native Flint runtime for REC packaging
alexhegit Oct 27, 2026
2d60fe0
docs: document native HIP packaging and serving
alexhegit Oct 27, 2026
c715d39
test: require gfx1151 native HIP hardware
alexhegit Oct 27, 2026
45b1b73
feat: require gfx1151 for native HIP serving
alexhegit Oct 27, 2026
9a18a3a
test: require local-only native checkpoints
alexhegit Oct 27, 2026
2706ed2
fix: enforce local-only native checkpoints
alexhegit Oct 27, 2026
708ee92
test: preserve existing runtime output directories
alexhegit Oct 27, 2026
1f3a5f3
fix: refuse existing runtime staging outputs
alexhegit Oct 27, 2026
681d37b
test: pin native checkpoint and context contract
alexhegit Oct 27, 2026
3fe75f1
fix: bind native manifests to Flint runtime limits
alexhegit Oct 27, 2026
54665a8
test: reject unbound native runtime executables
alexhegit Oct 27, 2026
e56e09c
fix: bind staged runtime to native Flint contract
alexhegit Oct 27, 2026
02b4cac
test: format native manifest pin table
alexhegit Oct 27, 2026
6fab64e
test: close native launch and packaging trust gaps
alexhegit Oct 27, 2026
8f0491a
fix: bind native launch and packaging to local runtime
alexhegit Oct 27, 2026
41ab162
test: reject self-attested native runtime forgeries
alexhegit Oct 27, 2026
3310dd1
fix: pin trusted native runtime provenance
alexhegit Oct 27, 2026
37a8b1f
test: reject caller-forged native trust manifests
alexhegit Oct 27, 2026
b3d0866
fix: root native trust in reviewed package policy
alexhegit Oct 27, 2026
b790dc8
chore: trust continuous batching Flint runtime
alexhegit Oct 28, 2026
73a2d0a
Merge origin/main into native Flint integration
alexhegit Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,35 @@ curl -fsSL https://raw.githubusercontent.com/recursive-oss/rec/main/install.sh |
This installs `rec` in `~/.local/bin` and adds it to your PATH for new
terminals. All other data lives under `~/.local/share/rec`.

### Native AMD/HIP development package

The native HIP target currently requires Linux/amd64, a ROCm installation,
and a locally persisted, verified Flint checkpoint. It does not download or
copy the checkpoint. Build `flint-serve`, then stage and package it with:

```bash
python3 packaging/build-flint-runtime.py \
--flint-serve /path/to/flint-serve \
--output /path/to/flint-runtime
python3 packaging/package.py \
--runtime-dir /path/to/flint-runtime \
--manifest packaging/flint-int4-hip.json \
--version VERSION \
--output /path/to/rec
```

Start the packaged binary against the persistent checkpoint directory:

```bash
/path/to/rec serve flint \
--model-dir /path/to/flint-int4 \
--context 4096
```

The HIP target currently serves the OpenAI-compatible endpoint. Anthropic
Messages support remains specific to the MLX runtime until native support is
implemented.


## Usage

Expand Down Expand Up @@ -99,17 +128,19 @@ sessions; remove those variables to keep yours.
To send a request directly to the OpenAI-compatible API:

```bash
curl -fsS http://127.0.0.1:11500/v1/chat/completions \
-H "Authorization: Bearer $(rec auth token)" \
read -r REC_TOKEN < <(rec auth token)
printf 'header = "Authorization: Bearer %s"\n' "$REC_TOKEN" | \
curl --config - -fsS http://127.0.0.1:11500/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"flint","messages":[{"role":"user","content":"Say hello."}],"max_tokens":128}'
unset REC_TOKEN
```

The response contains generated text in `choices`. For other clients, use
base URL `http://127.0.0.1:11500/v1`, model `flint`, and the API key from
`rec auth token`.

The Anthropic-compatible API is available at
For the MLX runtime, the Anthropic-compatible API is available at
`http://127.0.0.1:11500/v1/messages`. Point `ANTHROPIC_BASE_URL` at
`http://127.0.0.1:11500` and send the token from `rec auth token` as a bearer
token or as `x-api-key`. The model is `flint`.
Expand Down
27 changes: 24 additions & 3 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ import (
"syscall"
)

func validateModelSource(command string, m Manifest, modelDir string) error {
if m.Engine != "flint-native" {
return nil
}
if command == "pull" {
return fmt.Errorf("native Flint checkpoints are local-only; rec pull is unavailable")
}
if command == "serve" && modelDir == "" {
return fmt.Errorf("native Flint requires --model-dir DIR")
}
return nil
}

func run(args []string) error {
background := len(args) > 0 && args[0] == "_background"
if background {
Expand Down Expand Up @@ -92,10 +105,12 @@ func run(args []string) error {
manifest := flags.String("manifest", "", "")
runtimeDir := flags.String("runtime-dir", "", "")
var contextTokens int
var modelDir string
port := 11500
if command == "serve" {
flags.IntVar(&contextTokens, "context", 0, "")
flags.IntVar(&port, "port", port, "")
flags.StringVar(&modelDir, "model-dir", "", "")
}
flags.SetOutput(io.Discard)
if e := flags.Parse(rest); e != nil {
Expand Down Expand Up @@ -127,6 +142,12 @@ func run(args []string) error {
if *runtimeDir != "" && *manifest == "" {
return fmt.Errorf("--runtime-dir requires --manifest")
}
if modelDir != "" && m.Engine != "flint-native" {
return fmt.Errorf("--model-dir is supported only by the native Flint engine")
}
if e := validateModelSource(command, m, modelDir); e != nil {
return e
}
if m.Development {
newScreen(os.Stderr).step(warningStyle.Render("!"), "Development bundle: this is not a qualified Flint release.", "")
}
Expand All @@ -139,7 +160,7 @@ func run(args []string) error {
}
return e
}
return serve(ctx, p, m, *runtimeDir, port, os.Stderr, background)
return serve(ctx, p, m, *runtimeDir, modelDir, port, os.Stderr, background)
}

func doctor(p Paths, m Manifest, manifestErr error) error {
Expand All @@ -166,10 +187,10 @@ func doctor(p Paths, m Manifest, manifestErr error) error {
return manifestErr
}
if err := preflight(m); err != nil {
ui.failed("This Mac does not meet the requirements", "")
ui.failed("This system does not meet the requirements", "")
return err
}
ui.succeeded("This Mac meets the requirements", "")
ui.succeeded("This system meets the requirements", "")
if !modelFilesPresent(p, m) {
ui.step(warningStyle.Render("!"), displayName(m.Model)+" is not downloaded", "run rec pull")
return nil
Expand Down
21 changes: 21 additions & 0 deletions cli/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"os"
"reflect"
"testing"
)

Expand Down Expand Up @@ -97,6 +98,26 @@ func TestContextReachesEngine(t *testing.T) {
}
}

func TestNativeHIPLaunchContract(t *testing.T) {
m := releaseManifest(t)
m.Engine, m.OS, m.Arch, m.Backend = "flint-native", "linux", "amd64", "hip"
m.Context = 262144
executable, args, env := launchCommand(Paths{Config: "/config"}, m, "/runtime", "/model/config.json", 11500)
wantArgs := []string{
"--model", "/model", "--checkpoint-dir", "/model",
"--context-capacity", "262144", "--device-budget", "32592542688",
"--bind", "127.0.0.1", "--port", "11500", "--queue-limit", "8",
"--api-key-file", "/config/api-key", "--served-model", "flint",
"--enforce-tool-schemas",
}
if executable != "/runtime/flint-serve" || !reflect.DeepEqual(args, wantArgs) {
t.Fatalf("native launch mismatch\nexecutable: %q\nargs: %#v", executable, args)
}
if len(env) != 0 {
t.Fatalf("native launch must not place credentials in the environment: %#v", env)
}
}

func releaseManifest(t *testing.T) Manifest {
t.Helper()
b, err := os.ReadFile("../packaging/flint-int4-mlx.json")
Expand Down
31 changes: 31 additions & 0 deletions cli/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,37 @@ func ensureModel(ctx context.Context, p Paths, m Manifest, out io.Writer) (strin
return ensureModelWithClient(ctx, p, m, out, client)
}

// verifyLocalModel hashes the manifest-declared files in their existing
// directory. It never copies or downloads model bytes; the native runtime
// independently revalidates the pinned checkpoint before GPU admission.
func verifyLocalModel(root string, m Manifest, out io.Writer) (string, error) {
abs, err := filepath.Abs(root)
if err != nil {
return "", err
}
before, err := os.Lstat(abs)
if err != nil || !before.IsDir() || before.Mode()&os.ModeSymlink != 0 {
return "", fmt.Errorf("local model directory must be a real directory")
}
var total int64
for _, f := range m.Files {
total += f.Size
}
progress := newTransfer(out, "Verifying "+displayName(m.Model), "Verified "+displayName(m.Model), 0, total)
for _, f := range m.Files {
if err := verifyFileInto(filepath.Join(abs, f.Name), f, progress); err != nil {
progress.fail(err.Error())
return "", err
}
}
after, err := os.Lstat(abs)
if err != nil || !os.SameFile(before, after) {
return "", fmt.Errorf("local model directory changed during verification")
}
progress.succeed()
return filepath.Join(abs, m.Entry), nil
}

func ensureModelWithClient(ctx context.Context, p Paths, m Manifest, out io.Writer, client *http.Client) (string, error) {
id := modelDigest(m)
root := filepath.Join(p.Data, "models", id)
Expand Down
15 changes: 15 additions & 0 deletions cli/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ import (
)

func launchCommand(p Paths, m Manifest, runtimeDir, model string, port int) (string, []string, []string) {
if m.Engine == "flint-native" {
const base = uint64(25074426720)
const perToken = uint64(14 * 2048)
const logits = uint64(150144 * 13)
budget := base + uint64(m.Context-1)*perToken + logits
modelDir := filepath.Dir(model)
return filepath.Join(runtimeDir, "flint-serve"), []string{
"--model", modelDir, "--checkpoint-dir", modelDir,
"--context-capacity", strconv.Itoa(m.Context),
"--device-budget", strconv.FormatUint(budget, 10),
"--bind", "127.0.0.1", "--port", strconv.Itoa(port),
"--queue-limit", "8", "--api-key-file", filepath.Join(p.Config, "api-key"),
"--served-model", m.Model, "--enforce-tool-schemas",
}, nil
}
return filepath.Join(runtimeDir, runtimeServer), []string{
"serve", m.Model, "--model-dir", filepath.Dir(model), "--manifest", model,
"--port", strconv.Itoa(port), "--context-limit", strconv.Itoa(m.Context),
Expand Down
1 change: 1 addition & 0 deletions cli/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var commands = []commandHelp{
{"serve", "[MODEL]", "Start the model server in this terminal", append([][2]string{
{"--context TOKENS", "Total input and output tokens (default: set by the release)"},
{"--port PORT", "Local port (default: 11500)"},
{"--model-dir DIR", "Verify and use an existing local native checkpoint in place"},
}, developmentOptions...)},
{"status", "", "Show whether the server is running", nil},
{"stop", "", "Stop the server", nil},
Expand Down
10 changes: 10 additions & 0 deletions cli/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ func openingClient(name string, s Status) {
newScreen(os.Stderr).step(accentStyle.Render("→"), "Opening "+name, fmt.Sprintf("Flint at %s, %s tokens", s.Endpoint, formatTokens(s.Context)))
}

func validateLaunchAutostart(m Manifest) error {
if m.Engine == "flint-native" {
return fmt.Errorf("native Flint launch requires an existing local server; start it with rec serve flint --model-dir DIR")
}
return nil
}

func openCodeArgs(args []string) []string {
// V2's TUI selects its model from config; --model is a run-only flag.
// A private OpenCode server receives this process's Rec config and API key,
Expand Down Expand Up @@ -217,6 +224,9 @@ func launchReady(ctx context.Context, p Paths, out io.Writer) (Status, error) {
if e != nil {
return s, e
}
if e = validateLaunchAutostart(m); e != nil {
return s, e
}
if !modelFilesPresent(p, m) {
if e = preflight(m); e != nil {
return s, e
Expand Down
66 changes: 46 additions & 20 deletions cli/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,24 @@ type File struct {
}

type Manifest struct {
Engine string `json:"engine"`
Schema int `json:"schema"`
Model string `json:"model"`
Revision string `json:"revision"`
Development bool `json:"development"`
OS string `json:"os"`
Arch string `json:"arch"`
Backend string `json:"backend"`
Context int `json:"context"`
MaxContext int `json:"max_context,omitempty"`
MinRAM int64 `json:"min_ram_bytes"`
Files []File `json:"files"`
Entry string `json:"entry"`
RuntimeSHA string `json:"runtime_sha256"`
RuntimeFiles []File `json:"runtime_files"`
Engine string `json:"engine"`
Schema int `json:"schema"`
Model string `json:"model"`
Revision string `json:"revision"`
Development bool `json:"development"`
OS string `json:"os"`
Arch string `json:"arch"`
Backend string `json:"backend"`
Context int `json:"context"`
MaxContext int `json:"max_context,omitempty"`
MinRAM int64 `json:"min_ram_bytes"`
Files []File `json:"files"`
Entry string `json:"entry"`
RuntimeSHA string `json:"runtime_sha256"`
RuntimeFiles []File `json:"runtime_files"`
RuntimeBinarySHA string `json:"runtime_binary_sha256,omitempty"`
RuntimeSourceCommit string `json:"runtime_source_commit,omitempty"`
RuntimeSourceTree string `json:"runtime_source_tree,omitempty"`
}

func validHash(s string) bool {
Expand All @@ -68,6 +71,19 @@ func safeRuntimeName(s string) bool {
}

const runtimeServer = "rec-mlx"
const nativeFlintRevision = "3b93f9053b530488405ae9fca51d4900d6ce21f0"
const nativeFlintMaxContext = 262144

func runtimeServerFor(m Manifest) (string, error) {
switch {
case m.Engine == "mlx" && m.OS == "darwin" && m.Arch == "arm64" && m.Backend == "metal":
return runtimeServer, nil
case m.Engine == "flint-native" && m.OS == "linux" && m.Arch == "amd64" && m.Backend == "hip":
return "flint-serve", nil
default:
return "", fmt.Errorf("unsupported engine or target")
}
}

func decodeManifest(data []byte) (Manifest, error) {
var m Manifest
Expand All @@ -80,13 +96,23 @@ func decodeManifest(data []byte) (Manifest, error) {
if d.Decode(&extra) != io.EOF {
return m, fmt.Errorf("trailing manifest data")
}
// Each engine is one target. Adding an engine adds its own case here.
if m.Engine != "mlx" || m.OS != "darwin" || m.Arch != "arm64" || m.Backend != "metal" {
return m, fmt.Errorf("unsupported engine or target: rec runs the MLX engine on Apple Silicon")
// Each engine is one closed target tuple.
serverName, err := runtimeServerFor(m)
if err != nil {
return m, err
}
if m.MaxContext < 0 || (m.MaxContext > 0 && m.Context > m.MaxContext) {
return m, fmt.Errorf("invalid maximum context")
}
if m.Engine == "flint-native" &&
(m.Revision != nativeFlintRevision || m.MaxContext != nativeFlintMaxContext || m.Context > nativeFlintMaxContext) {
return m, fmt.Errorf("native Flint revision or context does not match the runtime contract")
}
if m.Engine == "flint-native" &&
(!validHash(m.RuntimeBinarySHA) || !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(m.RuntimeSourceCommit) ||
!regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(m.RuntimeSourceTree)) {
return m, fmt.Errorf("native Flint trusted runtime pins are missing")
}
if m.Schema != 1 || !safeName(m.Model) || m.Revision == "" || m.Context < 256 || m.MinRAM <= 0 || len(m.Files) == 0 {
return m, fmt.Errorf("incomplete model descriptor")
}
Expand Down Expand Up @@ -124,12 +150,12 @@ func decodeManifest(data []byte) (Manifest, error) {
}
server := false
for _, f := range m.RuntimeFiles {
if f.Name == runtimeServer && f.Executable {
if f.Name == serverName && f.Executable {
server = true
}
}
if !server {
return m, fmt.Errorf("runtime requires executable %s", runtimeServer)
return m, fmt.Errorf("runtime requires executable %s", serverName)
}
return m, nil
}
Expand Down
Loading