sipeed/picoclaw · error

create seahorse engine: %w

Error message

create seahorse engine: %w

What it means

seahorse.NewEngine failed while building the memory engine for the membench harness. NewEngine opens/creates the sqlite database at dbPath and initializes its schema, so this wrap carries whatever the storage layer reported: unusable path, locked/corrupt DB, schema-version mismatch, or permission problems. Without the engine, LOCOMO ingestion cannot start.

Source

Thrown at cmd/membench/ingest.go:31

// SeahorseIngestResult holds the results of ingesting into seahorse.
type SeahorseIngestResult struct {
	Engine  *seahorse.Engine
	ConvMap ConvMap // sampleID → conversationID
}

// IngestSeahorse loads all LOCOMO samples into a seahorse Engine.
// Returns the engine and a mapping from sampleID to conversationID for scoped retrieval.
func IngestSeahorse(ctx context.Context, samples []LocomoSample, dbPath string) (*SeahorseIngestResult, error) {
	noopFn := func(ctx context.Context, prompt string, opts seahorse.CompleteOptions) (string, error) {
		return "", nil
	}

	engine, err := seahorse.NewEngine(seahorse.Config{
		DBPath: dbPath,
	}, noopFn)
	if err != nil {
		return nil, fmt.Errorf("create seahorse engine: %w", err)
	}

	store := engine.GetRetrieval().Store()
	convMap := make(ConvMap)

	for si := range samples {
		sample := &samples[si]
		sessionKey := "locomo-" + sample.SampleID

		// Check if conversation already exists (idempotent)
		existing, _ := store.GetConversationBySessionKey(ctx, sessionKey)
		if existing != nil {
			convMap[sample.SampleID] = existing.ConversationID
			log.Printf("Skipping existing sample %s: convID=%d", sample.SampleID, existing.ConversationID)
			continue
		}

		turns := GetTurns(sample)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Ensure the parent directory of dbPath exists and is writable (mkdir -p)
  2. Kill the other process holding the DB, or wait for it to exit before rerunning
  3. If the DB is stale/corrupt from an older version, move it aside (mv bench.db bench.db.bak) and let ingestion rebuild
  4. Run the engine on a local filesystem, not NFS/sshfs, to avoid sqlite locking issues

Example fix

// before
engine, err := seahorse.NewEngine(seahorse.Config{DBPath: dbPath}, noopFn)

// after
if dir := filepath.Dir(dbPath); dir != "" {
    if err := os.MkdirAll(dir, 0o755); err != nil {
        return nil, fmt.Errorf("prepare db dir: %w", err)
    }
}
engine, err := seahorse.NewEngine(seahorse.Config{DBPath: dbPath}, noopFn)
Defensive patterns

Strategy: try-catch

Validate before calling

if dir := filepath.Dir(dbPath); dir != "" && dir != "." {
    if err := os.MkdirAll(dir, 0o755); err != nil {
        return fmt.Errorf("db parent dir: %w", err)
    }
}
if f, err := os.OpenFile(dbPath, os.O_RDWR|os.O_CREATE, 0o644); err != nil {
    return fmt.Errorf("db path not writable: %w", err)
} else {
    f.Close()
}

Try / catch

engine, err := seahorse.NewEngine(seahorse.Config{DBPath: dbPath}, noopFn)
if err != nil {
    if strings.Contains(err.Error(), "locked") || strings.Contains(err.Error(), "busy") {
        return nil, fmt.Errorf("db %s in use by another run: %w", dbPath, err)
    }
    return nil, fmt.Errorf("create seahorse engine: %w", err)
}

Prevention

When it happens

Trigger: dbPath's parent directory missing or unwritable; another membench/process holding the sqlite file (SQLITE_BUSY / database is locked); dbPath points at a non-DB file; old schema from a previous membench version; read-only filesystem.

Common situations: Rerunning membench while a previous run is still alive; pointing -db at a path on a read-only container volume; mixing engine versions against a stale DB file; path with a typo creating an unusable location.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/8ed95615bbc97648. Report an issue: GitHub.