---
title: Forge Extension
description: Mount Cortex into a Forge application as a first-class extension with automatic route registration, migrations, and DI.
---

Cortex ships a ready-made Forge extension in the `extension` package. It wires the engine, HTTP API, and lifecycle management into Forge's extension system with one call.

## Installation

```go
import "github.com/xraph/cortex/extension"
```

## Registering the extension

```go
package main

import (
    "github.com/xraph/forge"
    "github.com/xraph/cortex/extension"
    pgstore "github.com/xraph/cortex/store/postgres"
)

func main() {
    app := forge.New()

    cortexExt := extension.New(
        extension.WithStore(pgstore.New(bunDB)),
    )

    app.RegisterExtension(cortexExt)
    app.Run()
}
```

## What the extension does

| Lifecycle event | Behaviour |
|----------------|-----------|
| `Register` | Creates the engine, builds the API handler, registers the engine in the DI container via `vessel.Provide`, mounts all HTTP routes (unless disabled) |
| `Start` | Runs `store.Migrate(ctx)` (unless disabled), then calls `engine.Start(ctx)` |
| `Stop` | Calls `engine.Stop(ctx)` which emits `OnShutdown` to all plugins |
| `Health` | Calls `store.Ping(ctx)` to verify database connectivity |

## Extension options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `WithStore(s)` | `store.Store` | — | **Required.** Composite persistence store |
| `WithExtension(x)` | `plugin.Extension` | — | Register a Cortex plugin (lifecycle hooks) |
| `WithEngineOption(o)` | `engine.Option` | — | Pass any engine option through |
| `WithConfig(c)` | `extension.Config` | — | Set the full extension config |
| `WithGroveDatabase(name)` | `string` | `""` | Name of the grove.DB to resolve from DI; empty uses the default DB |
| `WithDisableRoutes()` | — | `false` | Skip HTTP route registration |
| `WithDisableMigrate()` | — | `false` | Skip auto-migration on Start |
| `WithBasePath(p)` | `string` | `""` | URL prefix for all Cortex routes |
| `WithLogger(l)` | `*slog.Logger` | `slog.Default()` | Structured logger |

## Extension config struct

```go
type Config struct {
    DisableRoutes        bool          // Skip HTTP route registration
    DisableMigrate       bool          // Skip auto-migration on start
    BasePath             string        // URL prefix for all cortex routes
    DefaultModel         string        // LLM model (default: "smart")
    DefaultMaxSteps      int           // Max reasoning steps per run (default: 25)
    DefaultMaxTokens     int           // Max output tokens per LLM call (default: 4096)
    DefaultTemperature   float64       // LLM sampling temperature (default: 0.7)
    DefaultReasoningLoop string        // Reasoning loop strategy (default: "react")
    ShutdownTimeout      time.Duration // Graceful shutdown timeout (default: 30s)
    RunConcurrency       int           // Max concurrent runs (default: 4)
    GroveDatabase        string        // Name of the grove.DB to resolve from DI
}
```

## YAML configuration

Cortex can be configured via YAML files under the `extensions.cortex` or `cortex` key. YAML configuration is merged with programmatic options (YAML takes precedence for most fields).

```yaml
# config.yaml
extensions:
  cortex:
    disable_routes: false
    disable_migrate: false
    base_path: "/cortex"
    default_model: "gpt-4o"
    default_max_steps: 50
    default_max_tokens: 8192
    default_temperature: 0.7
    default_reasoning_loop: "react"
    shutdown_timeout: "30s"
    run_concurrency: 8
    grove_database: "cortex"
```

Alternatively, the legacy top-level key is supported:

```yaml
# config.yaml
cortex:
  grove_database: "default"
  default_model: "claude-3-opus"
  run_concurrency: 4
```

### Config resolution order

1. YAML file values are loaded first (from `extensions.cortex` or `cortex` key).
2. Programmatic options (`WithConfig`, `WithDisableRoutes`, etc.) fill in any zero-valued fields.
3. Any remaining zero-valued fields are filled with defaults from `DefaultConfig()`.

### Requiring configuration

If you want Cortex to fail when no YAML config is found:

```go
cortexExt := extension.New(
    extension.WithRequireConfig(true),
)
```

## Disabling auto-migration

If you manage migrations separately (e.g. with a migration tool), disable auto-migration:

```go
cortexExt := extension.New(
    extension.WithStore(store),
    extension.WithDisableMigrate(),
)
```

## Disabling routes

If you only need the engine (e.g. using Cortex programmatically from another extension), disable route registration:

```go
cortexExt := extension.New(
    extension.WithStore(store),
    extension.WithDisableRoutes(),
)
```

## Accessing the engine from other extensions

After `Register` is called by Forge, `cortexExt.Engine()` returns the fully initialised engine:

```go
eng := cortexExt.Engine()
// Use eng.CreateAgent, eng.ListSkills, etc. from another extension
```

The engine is also available via Forge's DI container (powered by `vessel`):

```go
import "github.com/xraph/cortex/engine"

// Inside another extension's Register:
var eng *engine.Engine
if err := vessel.Resolve(fapp.Container(), &eng); err != nil {
    return err
}
```

## Tenant middleware

In a Forge app, tenant scope is typically set by middleware that reads the JWT or API key:

```go
func tenantMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        tenantID := r.Header.Get("X-Tenant-ID")
        appID := r.Header.Get("X-App-ID")
        ctx := cortex.WithTenant(r.Context(), tenantID)
        ctx = cortex.WithApp(ctx, appID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

router.Use(tenantMiddleware)
```

## Adding plugins

Register Cortex plugins (observability, audit hooks, etc.) alongside the extension:

```go
import (
    "github.com/xraph/cortex/extension"
    "github.com/xraph/cortex/observability"
    audithook "github.com/xraph/cortex/audit_hook"
)

metricsPlugin := observability.NewMetricsExtension()
auditPlugin := audithook.New(myRecorder)

cortexExt := extension.New(
    extension.WithStore(store),
    extension.WithExtension(metricsPlugin),
    extension.WithExtension(auditPlugin),
)
```

## Health checks

The extension implements `forge.Extension`'s `Health` method by pinging the store:

```go
func (e *Extension) Health(ctx context.Context) error {
    return e.eng.Store().Ping(ctx)
}
```

Forge surfaces this automatically in its health endpoint.

## Grove database integration

When your Forge app uses the [Grove extension](https://github.com/xraph/grove) to manage database connections, Cortex can automatically resolve a `grove.DB` from the DI container and construct the correct store backend based on the driver type.

### Using the default grove database

If the Grove extension registers a single database (or a default in multi-DB mode), use `WithGroveDatabase` with an empty name:

```go
ext := extension.New(
    extension.WithGroveDatabase(""),
)
```

### Using a named grove database

In multi-database setups, reference a specific database by name:

```go
ext := extension.New(
    extension.WithGroveDatabase("cortex"),
)
```

This resolves the grove.DB named `"cortex"` from the DI container and auto-constructs the matching store. The driver type is detected automatically -- you do not need to import individual store packages.

### Store resolution order

The extension resolves its store in this order:

1. **Explicit store** -- if `WithStore(s)` was called, it is used directly and grove is ignored.
2. **Grove database** -- if `WithGroveDatabase(name)` was called, the named or default `grove.DB` is resolved from DI.
3. **In-memory fallback** -- if neither is configured, an in-memory store is used.

## Complete example

```go
package main

import (
    "log"

    "github.com/xraph/forge"
    "github.com/xraph/grove"
    "github.com/xraph/grove/drivers/pgdriver"
    "github.com/xraph/cortex/extension"
    "github.com/xraph/cortex/observability"
    pgstore "github.com/xraph/cortex/store/postgres"
)

func main() {
    app := forge.New()

    db := grove.Open(pgdriver.New(
        pgdriver.WithDSN("postgres://user:pass@localhost:5432/cortex?sslmode=disable"),
    ))
    store := pgstore.New(db)

    cortexExt := extension.New(
        extension.WithStore(store),
        extension.WithExtension(observability.NewMetricsExtension()),
    )

    app.RegisterExtension(cortexExt)
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}
```

This gives you a production-ready Cortex deployment with:
- All 36 REST endpoints mounted
- Auto-migration on startup
- Health checks via `store.Ping`
- Prometheus-compatible metrics
- Graceful shutdown with plugin notification
