Forge Extension
Mount Weave into a Forge application as a first-class extension with automatic route registration and migrations.
Weave 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
import "github.com/xraph/weave/extension"Registering the extension
package main
import (
"github.com/xraph/forge"
"github.com/xraph/weave/extension"
)
func main() {
app := forge.New()
weaveExt := extension.New(
extension.WithGroveDatabase(""), // resolve default grove.DB from DI
)
app.RegisterExtension(weaveExt)
app.Run()
}Or with explicit stores:
weaveExt := extension.New(
extension.WithStore(pgstore.New(db)),
extension.WithVectorStore(pgvec.New(db)),
extension.WithEmbedder(embedder.NewOpenAI(
embedder.WithOpenAIKey(os.Getenv("OPENAI_API_KEY")),
)),
)What the extension does
| Lifecycle event | Behaviour |
|---|---|
Register | Creates the engine from the provided options |
Start | Runs store.Migrate and vectorstore.Migrate (unless disabled) |
RegisterRoutes | Mounts all Weave HTTP endpoints under the base path |
Stop | Calls engine.Stop -- emits OnShutdown to all plugins |
Extension options
| Option | Type | Default | Description |
|---|---|---|---|
WithStore(s) | store.Store | -- | MetadataStore (auto-resolved from grove if not set) |
WithVectorStore(v) | vectorstore.VectorStore | -- | VectorStore |
WithExtension(x) | plugins.Extension | -- | Lifecycle hook plugin (repeatable) |
WithEngineOption(opt) | engine.Option | -- | Pass engine option directly |
WithConfig(cfg) | Config | defaults | Full config struct |
WithDisableRoutes() | -- | false | Skip HTTP route registration |
WithDisableMigrate() | -- | false | Skip migrations on Start |
WithBasePath(path) | string | "/weave" | URL prefix for all weave routes |
WithGroveDatabase(name) | string | "" | Name of the grove.DB to resolve from DI |
WithRequireConfig(b) | bool | false | Require config in YAML files |
File-based configuration (YAML)
When running in a Forge application, the Weave extension automatically loads configuration from YAML config files. The extension looks for config under the following keys (in order):
extensions.weave-- standard Forge extension config namespaceweave-- top-level shorthand
Example YAML config
# forge.yaml
extensions:
weave:
disable_routes: false
disable_migrate: false
base_path: "/weave"
default_chunk_size: 512
default_chunk_overlap: 50
default_embedding_model: "text-embedding-3-small"
default_chunk_strategy: "recursive"
default_top_k: 10
shutdown_timeout: "30s"
ingest_concurrency: 4
grove_database: ""Or using the top-level shorthand:
# forge.yaml
weave:
default_chunk_size: 1024
default_embedding_model: "text-embedding-3-large"
ingest_concurrency: 8Config fields
| YAML Key | Type | Default | Description |
|---|---|---|---|
disable_routes | bool | false | Skip HTTP route registration |
disable_migrate | bool | false | Skip migrations on Start |
base_path | string | "/weave" | URL prefix for all weave routes |
default_chunk_size | int | 512 | Default tokens per chunk |
default_chunk_overlap | int | 50 | Default token overlap between chunks |
default_embedding_model | string | "text-embedding-3-small" | Default embedding model |
default_chunk_strategy | string | "recursive" | Default chunking strategy |
default_top_k | int | 10 | Default number of similarity search results |
shutdown_timeout | duration | "30s" | Max graceful shutdown wait time |
ingest_concurrency | int | 4 | Parallel ingest operations |
grove_database | string | "" | Named grove.DB to resolve from DI |
Merge behaviour
File-based configuration is merged with programmatic options. Programmatic options set via extension.New(...) take precedence over YAML values. This lets you set sensible defaults in YAML while overriding specific values in code.
Requiring configuration
If your deployment requires YAML config to be present, use WithRequireConfig:
ext := extension.New(
extension.WithRequireConfig(true), // error if no YAML config found
)Disabling auto-migration
If you manage migrations separately (e.g. with a migration tool), disable auto-migration:
weaveExt := extension.New(
extension.WithStore(store),
extension.WithDisableMigrate(),
)Accessing the engine from other extensions
After Register is called by Forge, weaveExt.Engine() returns the fully initialised engine:
eng := weaveExt.Engine()
// use eng.Ingest, eng.Retrieve, etc. from another extension or handlerTenant middleware
In a Forge app, tenant scope is typically set by middleware that reads the JWT or API key. Implement a Forge middleware that calls weave.WithTenant and weave.WithApp before passing the request to the Weave handlers:
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 := weave.WithTenant(r.Context(), tenantID)
ctx = weave.WithApp(ctx, appID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
router.Use(tenantMiddleware)Grove database integration
When your Forge app uses the Grove extension to manage database connections, Weave 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:
ext := extension.New(
extension.WithGroveDatabase(""),
extension.WithVectorStore(pgvec.New(pgxPool)),
extension.WithEmbedder(embedder.NewOpenAI(
embedder.WithOpenAIKey(os.Getenv("OPENAI_API_KEY")),
)),
)Using a named grove database
In multi-database setups, reference a specific database by name:
ext := extension.New(
extension.WithGroveDatabase("weave"),
extension.WithVectorStore(pgvec.New(pgxPool)),
extension.WithEmbedder(embedder.NewOpenAI(
embedder.WithOpenAIKey(os.Getenv("OPENAI_API_KEY")),
)),
)This resolves the grove.DB named "weave" 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:
- Explicit store -- if
WithStore(s)was called, it is used directly and grove is ignored. - Grove database -- if
WithGroveDatabase(name)was called, the named or defaultgrove.DBis resolved from DI. - In-memory fallback -- if neither is configured, an in-memory store is used.
Adding metrics
Register the observability extension alongside the Weave engine extension:
import "github.com/xraph/weave/observability"
metricsExt := observability.NewMetricsExtensionWithFactory(fapp.Metrics())
weaveExt := extension.New(
extension.WithStore(store),
extension.WithVectorStore(vecStore),
extension.WithExtension(metricsExt), // passed to engine.New internally
)