fix(brain): register RFC named actions

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 10:49:22 +00:00
parent 25ee288bd2
commit f7cbf58470
5 changed files with 502 additions and 0 deletions

334
pkg/brain/actions.go Normal file
View file

@ -0,0 +1,334 @@
// SPDX-License-Identifier: EUPL-1.2
package brain
import (
"context"
core "dappco.re/go/core"
)
type directOptions struct{}
// subsystem := brain.NewDirect()
// _ = subsystem.OnStartup(context.Background())
func (s *DirectSubsystem) OnStartup(_ context.Context) core.Result {
if s.ServiceRuntime == nil || s.Core() == nil {
return core.Result{OK: true}
}
c := s.Core()
c.Action("brain.remember", s.handleRemember).Description = "Store knowledge in OpenBrain"
c.Action("brain.recall", s.handleRecall).Description = "Recall knowledge from OpenBrain"
c.Action("brain.forget", s.handleForget).Description = "Forget knowledge in OpenBrain"
c.Action("brain.list", s.handleList).Description = "List knowledge in OpenBrain"
c.Action("message.send", s.handleSend).Description = "Send a direct message to another agent"
c.Action("message.inbox", s.handleInbox).Description = "Read direct messages for an agent"
c.Action("message.conversation", s.handleConversation).Description = "Read the conversation thread with another agent"
return core.Result{OK: true}
}
// result := c.Action("brain.remember").Run(ctx, core.NewOptions(
//
// core.Option{Key: "content", Value: "Use OpenBrain for cross-agent context"},
// core.Option{Key: "type", Value: "architecture"},
//
// ))
func (s *DirectSubsystem) handleRemember(ctx context.Context, options core.Options) core.Result {
input := RememberInput{
Content: actionStringValue(options, "content"),
Type: actionStringValue(options, "type"),
Tags: actionStringSliceValue(options, "tags"),
Project: actionStringValue(options, "project"),
Confidence: actionFloatValue(options, "confidence"),
Supersedes: actionStringValue(options, "supersedes"),
ExpiresIn: actionIntValue(options, "expires_in", "expiresIn"),
}
_, output, err := s.remember(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("brain.recall").Run(ctx, core.NewOptions(
//
// core.Option{Key: "query", Value: "OpenBrain architecture"},
// core.Option{Key: "top_k", Value: 5},
//
// ))
func (s *DirectSubsystem) handleRecall(ctx context.Context, options core.Options) core.Result {
input := RecallInput{
Query: actionStringValue(options, "query"),
TopK: actionIntValue(options, "top_k", "topK"),
Filter: recallFilterFromOptions(options),
}
_, output, err := s.recall(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("brain.forget").Run(ctx, core.NewOptions(
//
// core.Option{Key: "id", Value: "mem-123"},
//
// ))
func (s *DirectSubsystem) handleForget(ctx context.Context, options core.Options) core.Result {
input := ForgetInput{
ID: actionStringValue(options, "id"),
Reason: actionStringValue(options, "reason"),
}
_, output, err := s.forget(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("brain.list").Run(ctx, core.NewOptions(
//
// core.Option{Key: "project", Value: "agent"},
// core.Option{Key: "limit", Value: 10},
//
// ))
func (s *DirectSubsystem) handleList(ctx context.Context, options core.Options) core.Result {
input := ListInput{
Project: actionStringValue(options, "project"),
Type: actionStringValue(options, "type"),
AgentID: actionStringValue(options, "agent_id", "agent"),
Limit: actionIntValue(options, "limit"),
}
_, output, err := s.list(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("message.send").Run(ctx, core.NewOptions(
//
// core.Option{Key: "to", Value: "charon"},
// core.Option{Key: "content", Value: "Deploy complete"},
//
// ))
func (s *DirectSubsystem) handleSend(ctx context.Context, options core.Options) core.Result {
input := SendInput{
To: actionStringValue(options, "to"),
Content: actionStringValue(options, "content"),
Subject: actionStringValue(options, "subject"),
}
_, output, err := s.sendMessage(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("message.inbox").Run(ctx, core.NewOptions(
//
// core.Option{Key: "agent", Value: "cladius"},
//
// ))
func (s *DirectSubsystem) handleInbox(ctx context.Context, options core.Options) core.Result {
input := InboxInput{
Agent: actionStringValue(options, "agent"),
}
_, output, err := s.inbox(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("message.conversation").Run(ctx, core.NewOptions(
//
// core.Option{Key: "agent", Value: "charon"},
//
// ))
func (s *DirectSubsystem) handleConversation(ctx context.Context, options core.Options) core.Result {
input := ConversationInput{
Agent: actionStringValue(options, "agent"),
}
_, output, err := s.conversation(ctx, nil, input)
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
func recallFilterFromOptions(options core.Options) RecallFilter {
filter := recallFilterValue(actionOptionValue(options, "filter"))
if filter.Project == "" {
filter.Project = actionStringValue(options, "project")
}
if filter.Type == nil {
filter.Type = actionOptionValue(options, "type")
}
if filter.AgentID == "" {
filter.AgentID = actionStringValue(options, "agent_id", "agent")
}
if filter.MinConfidence == 0 {
filter.MinConfidence = actionFloatValue(options, "min_confidence", "minConfidence")
}
return filter
}
func recallFilterValue(value any) RecallFilter {
switch typed := value.(type) {
case RecallFilter:
return typed
case map[string]any:
return RecallFilter{
Project: actionStringFromAny(typed["project"]),
Type: typed["type"],
AgentID: actionStringFromAny(typed["agent_id"]),
MinConfidence: actionFloatFromAny(typed["min_confidence"]),
}
case map[string]string:
return RecallFilter{
Project: actionStringFromAny(typed["project"]),
Type: typed["type"],
AgentID: actionStringFromAny(typed["agent_id"]),
}
default:
if text := actionStringFromAny(value); text != "" {
return RecallFilter{Type: text}
}
}
return RecallFilter{}
}
func actionOptionValue(options core.Options, keys ...string) any {
for _, key := range keys {
result := options.Get(key)
if result.OK {
return result.Value
}
}
return nil
}
func actionStringValue(options core.Options, keys ...string) string {
return actionStringFromAny(actionOptionValue(options, keys...))
}
func actionIntValue(options core.Options, keys ...string) int {
return actionIntFromAny(actionOptionValue(options, keys...))
}
func actionFloatValue(options core.Options, keys ...string) float64 {
return actionFloatFromAny(actionOptionValue(options, keys...))
}
func actionStringSliceValue(options core.Options, keys ...string) []string {
return actionStringSliceFromAny(actionOptionValue(options, keys...))
}
func actionStringFromAny(value any) string {
switch typed := value.(type) {
case string:
return core.Trim(typed)
case int:
return core.Sprint(typed)
case int64:
return core.Sprint(typed)
case float64:
return core.Sprint(int(typed))
case bool:
return core.Sprint(typed)
}
return ""
}
func actionIntFromAny(value any) int {
switch typed := value.(type) {
case int:
return typed
case int64:
return int(typed)
case float64:
return int(typed)
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return 0
}
var parsed int
if result := core.JSONUnmarshalString(core.Concat("{\"n\":", trimmed, "}"), &struct {
N *int `json:"n"`
}{N: &parsed}); result.OK {
return parsed
}
}
return 0
}
func actionFloatFromAny(value any) float64 {
switch typed := value.(type) {
case float64:
return typed
case float32:
return float64(typed)
case int:
return float64(typed)
case int64:
return float64(typed)
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return 0
}
var parsed float64
if result := core.JSONUnmarshalString(core.Concat("{\"n\":", trimmed, "}"), &struct {
N *float64 `json:"n"`
}{N: &parsed}); result.OK {
return parsed
}
}
return 0
}
func actionStringSliceFromAny(value any) []string {
switch typed := value.(type) {
case []string:
return cleanActionStrings(typed)
case []any:
var values []string
for _, item := range typed {
if text := actionStringFromAny(item); text != "" {
values = append(values, text)
}
}
return cleanActionStrings(values)
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return nil
}
if core.HasPrefix(trimmed, "[") {
var values []string
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return cleanActionStrings(values)
}
}
return cleanActionStrings(core.Split(trimmed, ","))
default:
if text := actionStringFromAny(value); text != "" {
return []string{text}
}
}
return nil
}
func cleanActionStrings(values []string) []string {
var cleaned []string
for _, value := range values {
trimmed := core.Trim(value)
if trimmed != "" {
cleaned = append(cleaned, trimmed)
}
}
return cleaned
}

View file

@ -0,0 +1,20 @@
// SPDX-License-Identifier: EUPL-1.2
package brain
import (
"context"
core "dappco.re/go/core"
)
func ExampleRegister_actions() {
c := core.New(core.WithService(Register))
c.ServiceStartup(context.Background(), nil)
core.Println(c.Action("brain.list").Exists())
core.Println(c.Action("message.send").Exists())
// Output:
// true
// true
}

146
pkg/brain/actions_test.go Normal file
View file

@ -0,0 +1,146 @@
// SPDX-License-Identifier: EUPL-1.2
package brain
import (
"context"
"net/http"
"net/http/httptest"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestActions_OnStartup_Good(t *testing.T) {
t.Setenv("CORE_BRAIN_URL", "https://api.lthn.sh")
t.Setenv("CORE_BRAIN_KEY", "test-key")
c := core.New(core.WithService(Register))
result := c.ServiceStartup(context.Background(), nil)
require.True(t, result.OK)
assert.True(t, c.Action("brain.remember").Exists())
assert.True(t, c.Action("brain.recall").Exists())
assert.True(t, c.Action("brain.forget").Exists())
assert.True(t, c.Action("brain.list").Exists())
assert.True(t, c.Action("message.send").Exists())
assert.True(t, c.Action("message.inbox").Exists())
assert.True(t, c.Action("message.conversation").Exists())
}
func TestActions_HandleList_Good(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method)
assert.Equal(t, "/v1/brain/list", r.URL.Path)
assert.Equal(t, "agent", r.URL.Query().Get("project"))
assert.Equal(t, "decision", r.URL.Query().Get("type"))
assert.Equal(t, "cladius", r.URL.Query().Get("agent_id"))
assert.Equal(t, "2", r.URL.Query().Get("limit"))
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(core.JSONMarshalString(map[string]any{
"memories": []any{
map[string]any{
"id": "mem-1",
"content": "Use brain.list for filtered history",
"type": "decision",
"project": "agent",
"agent_id": "cladius",
"confidence": 0.9,
"created_at": "2026-03-31T00:00:00Z",
"updated_at": "2026-03-31T00:00:00Z",
},
},
})))
}))
defer srv.Close()
t.Setenv("CORE_BRAIN_URL", srv.URL)
t.Setenv("CORE_BRAIN_KEY", "test-key")
c := core.New(core.WithService(Register))
result := c.ServiceStartup(context.Background(), nil)
require.True(t, result.OK)
actionResult := c.Action("brain.list").Run(context.Background(), core.NewOptions(
core.Option{Key: "project", Value: "agent"},
core.Option{Key: "type", Value: "decision"},
core.Option{Key: "agent_id", Value: "cladius"},
core.Option{Key: "limit", Value: 2},
))
require.True(t, actionResult.OK)
output, ok := actionResult.Value.(ListOutput)
require.True(t, ok)
assert.True(t, output.Success)
assert.Equal(t, 1, output.Count)
require.Len(t, output.Memories, 1)
assert.Equal(t, "mem-1", output.Memories[0].ID)
}
func TestActions_HandleList_Bad(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":"down"}`))
}))
defer srv.Close()
t.Setenv("CORE_BRAIN_URL", srv.URL)
t.Setenv("CORE_BRAIN_KEY", "test-key")
c := core.New(core.WithService(Register))
result := c.ServiceStartup(context.Background(), nil)
require.True(t, result.OK)
actionResult := c.Action("brain.list").Run(context.Background(), core.NewOptions())
require.False(t, actionResult.OK)
err, ok := actionResult.Value.(error)
require.True(t, ok)
assert.Contains(t, err.Error(), "API call failed")
}
func TestActions_HandleRecall_Ugly_FilterMap(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method)
assert.Equal(t, "/v1/brain/recall", r.URL.Path)
var body map[string]any
require.True(t, core.JSONUnmarshalString(core.ReadAll(r.Body).Value.(string), &body).OK)
assert.Equal(t, "architecture", body["query"])
assert.Equal(t, float64(3), body["top_k"])
assert.Equal(t, "agent", body["project"])
assert.Equal(t, "decision", body["type"])
assert.Equal(t, "clotho", body["agent_id"])
assert.Equal(t, 0.75, body["min_confidence"])
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(core.JSONMarshalString(map[string]any{"memories": []any{}})))
}))
defer srv.Close()
t.Setenv("CORE_BRAIN_URL", srv.URL)
t.Setenv("CORE_BRAIN_KEY", "test-key")
c := core.New(core.WithService(Register))
result := c.ServiceStartup(context.Background(), nil)
require.True(t, result.OK)
actionResult := c.Action("brain.recall").Run(context.Background(), core.NewOptions(
core.Option{Key: "query", Value: "architecture"},
core.Option{Key: "top_k", Value: 3},
core.Option{Key: "filter", Value: map[string]any{
"project": "agent",
"type": "decision",
"agent_id": "clotho",
"min_confidence": 0.75,
}},
))
require.True(t, actionResult.OK)
output, ok := actionResult.Value.(RecallOutput)
require.True(t, ok)
assert.True(t, output.Success)
assert.Equal(t, 0, output.Count)
}

View file

@ -16,6 +16,7 @@ import (
// subsystem := brain.NewDirect()
// core.Println(subsystem.Name()) // "brain"
type DirectSubsystem struct {
*core.ServiceRuntime[directOptions]
apiURL string
apiKey string
}

View file

@ -11,5 +11,6 @@ import (
// core.Println(subsystem.OK) // true
func Register(c *core.Core) core.Result {
subsystem := NewDirect()
subsystem.ServiceRuntime = core.NewServiceRuntime(c, directOptions{})
return core.Result{Value: subsystem, OK: true}
}