Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 2 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@ PLAN.md decisions + execution + open questions
docs/ published docs
skills/ in-project agent skills (vendored, no external links)
bin/ self-describing tools bin/{subject}/{method}.go (shebang)
bin/brain/ search.go (deduction); libs in internal/brain
bin/brain/ search.go, serve.go; libs in internal/brain and internal/httpapi
internal/ shared Go (brain/rank is cgo-free)
bin/serve.go async Go HTTP server entry (self-executing go run shebang)
bin/watch/ corpus watcher Go package (mtimes, no inotify deps)
bin/server/ async Go HTTP server (goroutines, bounded worker pool)
bin/watch/ corpus watcher (internal via bin/brain/watch later)
bin/mail/ mail pipeline: sync (Go), import (md), index_mail (rebuild)
bin/tools/ vendored python libs behind bin/* (kblib, yamlout, websearch)
bin/docker-entrypoint container entrypoint (brain index|search|serve|watch)
Expand Down
26 changes: 26 additions & 0 deletions bin/brain/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//usr/bin/env go run -tags=brain_serve "$0" "$@"; exit
//go:build brain_serve
//
// bin/brain/serve.go - HTTP API for the 2dph brain.
//
// KB_ROOT=/path/to/2dph ./bin/brain/serve.go
// KB_SEARCH_CMD=... KB_WORKERS=4 KB_PORT=8630 ./bin/brain/serve.go
//
// Default search backend is var/bin/brain-search (Go), not Python.
// NOTE: never run `gofmt -w` on this file — it breaks the shebang.
package main

import (
"os"

"github.com/eSlider/2dph/internal/httpapi"
)

func main() {
if os.Getenv("KB_ROOT") == "" {
if wd, err := os.Getwd(); err == nil {
os.Setenv("KB_ROOT", wd)
}
}
httpapi.Run()
}
2 changes: 2 additions & 0 deletions bin/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Deprecated shebang mains at bin root (serve.go is tagged brain_serve).
package main
21 changes: 8 additions & 13 deletions bin/serve.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
//usr/bin/env go run "$0" "$@"; exit
// bin/serve.go - async Go HTTP server for the 2dph brain (see bin/server).
//usr/bin/env go run -tags=brain_serve "$0" "$@"; exit
//go:build brain_serve
//
// KB_ROOT=/path/to/2dph ./bin/serve.go # serve the brain
// KB_SEARCH_CMD=... KB_WORKERS=4 KB_PORT=8630 ./bin/serve.go
//
// Shebang trick: the first line is a Go `//` comment; when executed, env runs
// `go run "$0"` so this file doubles as an executable script. The real code
// lives in the importable package (module path, never a relative import).
// NOTE: never run `gofmt -w` on this file - it rewrites `//usr/bin/env` to
// `// usr/...` and breaks the shebang.
// bin/serve.go — deprecated; use bin/brain/serve.go.
package main

import (
"fmt"
"os"

"github.com/eSlider/2dph/bin/server"
"github.com/eSlider/2dph/internal/httpapi"
)

func main() {
if env := os.Getenv("KB_ROOT"); env == "" {
fmt.Fprintln(os.Stderr, "bin/serve.go is deprecated; use bin/brain/serve.go")
if os.Getenv("KB_ROOT") == "" {
if wd, err := os.Getwd(); err == nil {
os.Setenv("KB_ROOT", wd)
}
}
server.Run()
httpapi.Run()
}
34 changes: 16 additions & 18 deletions bin/server/server.go → internal/httpapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@
//
// Async by design: every request runs on its own goroutine, and CPU-heavy
// searches are serialized through a bounded worker pool (a counting
// semaphore) so N requests can't spawn N Python interpreters at once.
// semaphore) so N requests can't spawn N search processes at once.
//
// Used by bin/serve.go which is a self-executing shebang script:
//
// ///usr/bin/env go run "$0" "$@"; exit
// package main
// import "github.com/eSlider/2dph/bin/server"
// func main() { server.Run() }
package server
// Used by bin/brain/serve.go.
package httpapi

import (
"context"
Expand Down Expand Up @@ -100,8 +95,8 @@ func writeRaw(w http.ResponseWriter, code int, body []byte) {
w.Write(body)
}

// brainSearcher shells out to bin/kb/search --json. A single python search
// is bounded and short-lived; the worker pool keeps at most N live.
// brainSearcher shells out to the Go brain-search binary (not Python).
// A single search is bounded and short-lived; the worker pool keeps at most N live.
type brainSearcher struct {
cmdPath string
timeout time.Duration
Expand All @@ -122,15 +117,18 @@ func (b *brainSearcher) Search(ctx context.Context, query string, limit int) ([]
return out, nil
}

// Run starts the HTTP server. Reads env: KB_SEARCH_CMD (default bin/kb/search,
// relative to the repo root given by KB_ROOT), KB_WORKERS (default 4), KB_PORT
// (default 8630).
func defaultSearchCmd(root string) string {
if env := os.Getenv("KB_SEARCH_CMD"); env != "" {
return env
}
return filepath.Join(root, "var", "bin", "brain-search")
}

// Run starts the HTTP server. Reads env: KB_SEARCH_CMD (default
// $KB_ROOT/var/bin/brain-search), KB_WORKERS (default 4), KB_PORT (default 8630).
func Run() {
root := os.Getenv("KB_ROOT")
searchPath := os.Getenv("KB_SEARCH_CMD")
if searchPath == "" {
searchPath = filepath.Join(root, "bin", "kb", "search")
}
searchPath := defaultSearchCmd(root)
workers := 4
if raw := os.Getenv("KB_WORKERS"); raw != "" {
if n, err := strconv.Atoi(raw); err == nil && n > 0 {
Expand All @@ -147,7 +145,7 @@ func Run() {
searcher := &brainSearcher{cmdPath: searchPath, timeout: 60 * time.Second}
handler := NewServer(searcher, workers)
addr := "127.0.0.1:" + strconv.Itoa(port)
log.Printf("serve: %s (workers=%d)", addr, workers)
log.Printf("serve: %s (workers=%d cmd=%s)", addr, workers, searchPath)
if err := http.ListenAndServe(addr, handler); err != nil {
log.Fatal(err)
}
Expand Down
14 changes: 13 additions & 1 deletion bin/server/server_test.go → internal/httpapi/server_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package server
package httpapi

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"sync"
"sync/atomic"
"testing"
Expand Down Expand Up @@ -141,6 +142,17 @@ func TestSearchRejectsBadLimit(t *testing.T) {
}
}

func TestDefaultSearchCmdIsBrainNotPython(t *testing.T) {
t.Setenv("KB_SEARCH_CMD", "")
cmd := defaultSearchCmd("/repo")
if strings.Contains(strings.ToLower(cmd), "python") {
t.Fatalf("search path still python: %s", cmd)
}
if !strings.Contains(cmd, "brain") {
t.Fatalf("search path must be the Go brain binary, got %s", cmd)
}
}

func TestSearchTimeout(t *testing.T) {
fs := &fakeSearcher{delay: time.Second}
h := NewServer(fs, 1)
Expand Down
Loading