Skip to content

Commit ac2311b

Browse files
committed
feat: add search functionality and count methods for various entities
- Implemented search functionality for agents, behaviors, personas, skills, traits, and runs using regex matching. - Added Count methods for agents, behaviors, checkpoints, personas, runs, skills, and traits to return the total number of items matching the specified filters. - Created migrations for new collections in MongoDB for agents, runs, steps, tool calls, memories, checkpoints, skills, traits, behaviors, and personas.
1 parent 7528950 commit ac2311b

197 files changed

Lines changed: 51562 additions & 193 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help build run test clean fmt lint lint-fix vet tidy deps install dev hot check coverage b r t c f l lf v check-deps
1+
.PHONY: help build run test clean fmt lint lint-fix vet tidy deps install dev hot check coverage b r t c f l lf v check-deps templ templ-watch
22

33
# Default target
44
.DEFAULT_GOAL := help
@@ -53,6 +53,10 @@ help:
5353
@echo " make docs - Serve documentation locally"
5454
@echo " make docs-build - Build documentation"
5555
@echo ""
56+
@echo "$(GREEN)Code Generation:$(NC)"
57+
@echo " make templ - Generate templ files"
58+
@echo " make templ-watch - Watch and regenerate templ files on change"
59+
@echo ""
5660
@echo "$(GREEN)Other:$(NC)"
5761
@echo " make all - Run check, test, and build"
5862
@echo " make help (h) - Show this help message"
@@ -100,7 +104,7 @@ clean c:
100104
fmt f:
101105
@echo "$(BLUE)Formatting code...$(NC)"
102106
@gofmt -s -w .
103-
@command -v goimports >/dev/null 2>&1 && goimports -w -local github.com/xraph/ctrlplane . || echo "$(YELLOW)goimports not found, skipping (run: go install golang.org/x/tools/cmd/goimports@latest)$(NC)"
107+
@command -v goimports >/dev/null 2>&1 && goimports -w -local github.com/xraph/cortex . || echo "$(YELLOW)goimports not found, skipping (run: go install golang.org/x/tools/cmd/goimports@latest)$(NC)"
104108
@echo "$(GREEN)✓ Formatting complete$(NC)"
105109

106110
## lint (l): Run linter
@@ -178,6 +182,8 @@ deps:
178182
@go install github.com/cosmtrek/air@latest
179183
@echo "Installing golangci-lint..."
180184
@command -v golangci-lint >/dev/null 2>&1 || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin
185+
@echo "Installing templ..."
186+
@go install github.com/a-h/templ/cmd/templ@latest
181187
@echo "$(GREEN)✓ Development dependencies installed$(NC)"
182188

183189
## check-deps: Check if required tools are installed
@@ -186,6 +192,7 @@ check-deps:
186192
@command -v goimports >/dev/null 2>&1 && echo "$(GREEN)✓ goimports$(NC)" || echo "$(YELLOW)✗ goimports (run: make deps)$(NC)"
187193
@command -v golangci-lint >/dev/null 2>&1 && echo "$(GREEN)✓ golangci-lint$(NC)" || echo "$(YELLOW)✗ golangci-lint (run: make deps)$(NC)"
188194
@command -v air >/dev/null 2>&1 && echo "$(GREEN)✓ air$(NC)" || echo "$(YELLOW)✗ air (run: make deps)$(NC)"
195+
@command -v templ >/dev/null 2>&1 && echo "$(GREEN)✓ templ$(NC)" || echo "$(YELLOW)✗ templ (run: make deps)$(NC)"
189196

190197
## mod-download: Download modules
191198
mod-download:
@@ -210,8 +217,21 @@ docs-build:
210217
@cd docs && pnpm install && pnpm build
211218
@echo "$(GREEN)✓ Documentation built$(NC)"
212219

213-
## all: Run check, test, and build
214-
all: check test build
220+
## templ: Generate templ files
221+
templ:
222+
@echo "$(BLUE)Generating templ files...$(NC)"
223+
@command -v templ >/dev/null 2>&1 || { echo "$(RED)templ not found. Install: go install github.com/a-h/templ/cmd/templ@latest$(NC)"; exit 1; }
224+
templ generate ./dashboard/...
225+
@echo "$(GREEN)✓ Templ generation complete$(NC)"
226+
227+
## templ-watch: Watch and regenerate templ files
228+
templ-watch:
229+
@echo "$(BLUE)Watching templ files...$(NC)"
230+
@command -v templ >/dev/null 2>&1 || { echo "$(RED)templ not found. Install: go install github.com/a-h/templ/cmd/templ@latest$(NC)"; exit 1; }
231+
templ generate --watch ./dashboard/...
232+
233+
## all: Run templ generate, check, test, and build
234+
all: templ check test build
215235
@echo "$(GREEN)✓ All tasks complete$(NC)"
216236

217237
# Short aliases

agent/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ type Store interface {
5050
Update(ctx context.Context, config *Config) error
5151
Delete(ctx context.Context, agentID id.AgentID) error
5252
List(ctx context.Context, filter *ListFilter) ([]*Config, error)
53+
CountAgents(ctx context.Context, filter *ListFilter) (int64, error)
5354
}
5455

55-
// ListFilter controls pagination for agent listing.
56+
// ListFilter controls pagination and filtering for agent listing.
5657
type ListFilter struct {
5758
AppID string
59+
Search string
5860
Limit int
5961
Offset int
6062
}

api/agent_handler.go

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import (
88

99
"github.com/xraph/cortex"
1010
"github.com/xraph/cortex/agent"
11+
"github.com/xraph/cortex/engine"
1112
"github.com/xraph/cortex/id"
1213
)
1314

1415
func (a *API) registerAgentRoutes(router forge.Router) error {
15-
g := router.Group("/cortex", forge.WithGroupTags("agents"))
16+
g := router.Group("", forge.WithGroupTags("agents"))
1617

1718
if err := g.POST("/agents", a.createAgent,
1819
forge.WithSummary("Create agent"),
@@ -87,6 +88,16 @@ func (a *API) registerAgentRoutes(router forge.Router) error {
8788
return fmt.Errorf("register agent routes: %w", err)
8889
}
8990

91+
if err := g.POST("/agents/:name/preview-prompt", a.previewPrompt,
92+
forge.WithSummary("Preview prompt"),
93+
forge.WithDescription("Returns the computed system prompt for the agent without executing."),
94+
forge.WithOperationID("previewPrompt"),
95+
forge.WithResponseSchema(http.StatusOK, "Computed prompt", &PreviewPromptResponse{}),
96+
forge.WithErrorResponses(),
97+
); err != nil {
98+
return fmt.Errorf("register agent routes: %w", err)
99+
}
100+
90101
return nil
91102
}
92103

@@ -132,7 +143,7 @@ func (a *API) getAgent(ctx forge.Context, _ *GetAgentRequest) (*agent.Config, er
132143
return cfg, ctx.JSON(http.StatusOK, cfg)
133144
}
134145

135-
func (a *API) listAgents(ctx forge.Context, req *ListAgentsRequest) ([]*agent.Config, error) {
146+
func (a *API) listAgents(ctx forge.Context, req *ListAgentsRequest) (*ListAgentsResponse, error) {
136147
agents, err := a.eng.ListAgents(ctx.Context(), &agent.ListFilter{
137148
AppID: cortex.AppFromContext(ctx.Context()),
138149
Limit: defaultLimit(req.Limit),
@@ -141,7 +152,8 @@ func (a *API) listAgents(ctx forge.Context, req *ListAgentsRequest) ([]*agent.Co
141152
if err != nil {
142153
return nil, fmt.Errorf("list agents: %w", err)
143154
}
144-
return agents, ctx.JSON(http.StatusOK, agents)
155+
resp := &ListAgentsResponse{Items: agents}
156+
return resp, ctx.JSON(http.StatusOK, resp)
145157
}
146158

147159
func (a *API) updateAgent(ctx forge.Context, req *UpdateAgentRequest) (*agent.Config, error) {
@@ -210,12 +222,91 @@ func (a *API) deleteAgent(ctx forge.Context, _ *DeleteAgentRequest) (*struct{},
210222
return nil, ctx.NoContent(http.StatusNoContent)
211223
}
212224

213-
func (a *API) runAgent(_ forge.Context, _ *RunAgentRequest) (*struct{}, error) {
214-
// TODO: implement agent execution in phase 2
215-
return nil, forge.NotFound("agent execution not yet implemented")
225+
func (a *API) runAgent(ctx forge.Context, req *RunAgentRequest) (*RunAgentResponse, error) {
226+
if req.Input == "" {
227+
return nil, forge.BadRequest("input is required")
228+
}
229+
230+
appID := cortex.AppFromContext(ctx.Context())
231+
r, err := a.eng.RunAgent(ctx.Context(), appID, req.Name, req.Input, mapOverrides(req.Overrides))
232+
if err != nil {
233+
return nil, mapStoreError(err)
234+
}
235+
236+
var durationMs int64
237+
if r.StartedAt != nil && r.CompletedAt != nil {
238+
durationMs = r.CompletedAt.Sub(*r.StartedAt).Milliseconds()
239+
}
240+
241+
resp := &RunAgentResponse{
242+
RunID: r.ID.String(),
243+
Output: r.Output,
244+
State: string(r.State),
245+
StepCount: r.StepCount,
246+
TokensUsed: r.TokensUsed,
247+
DurationMs: durationMs,
248+
}
249+
return resp, ctx.JSON(http.StatusOK, resp)
216250
}
217251

218-
func (a *API) streamAgent(_ forge.Context, _ *StreamAgentRequest) (*struct{}, error) {
219-
// TODO: implement streaming agent execution in phase 2
220-
return nil, forge.NotFound("agent streaming not yet implemented")
252+
func (a *API) streamAgent(ctx forge.Context, req *StreamAgentRequest) (*struct{}, error) {
253+
if req.Input == "" {
254+
return nil, forge.BadRequest("input is required")
255+
}
256+
257+
ctx.SetHeader("Content-Type", "text/event-stream")
258+
ctx.SetHeader("Cache-Control", "no-cache")
259+
ctx.SetHeader("Connection", "keep-alive")
260+
ctx.SetHeader("X-Accel-Buffering", "no")
261+
262+
appID := cortex.AppFromContext(ctx.Context())
263+
events := make(chan engine.StreamEvent, 64)
264+
265+
if err := a.eng.StreamAgent(ctx.Context(), appID, req.Name, req.Input, mapOverrides(req.Overrides), events); err != nil {
266+
return nil, mapStoreError(err)
267+
}
268+
269+
for evt := range events {
270+
if err := ctx.WriteSSE(string(evt.Type), evt.Data); err != nil {
271+
break
272+
}
273+
}
274+
275+
return nil, nil
276+
}
277+
278+
func (a *API) previewPrompt(ctx forge.Context, _ *PreviewPromptRequest) (*PreviewPromptResponse, error) {
279+
appID := cortex.AppFromContext(ctx.Context())
280+
ag, err := a.eng.GetAgentByName(ctx.Context(), appID, ctx.Param("name"))
281+
if err != nil {
282+
return nil, mapStoreError(err)
283+
}
284+
285+
// Use the engine's prompt builder for a consistent preview.
286+
prompt := a.eng.BuildSystemPrompt(ctx.Context(), ag, nil)
287+
288+
resp := &PreviewPromptResponse{
289+
Prompt: prompt,
290+
}
291+
return resp, ctx.JSON(http.StatusOK, resp)
292+
}
293+
294+
// mapOverrides converts API-layer overrides to engine-layer overrides.
295+
func mapOverrides(o *AgentOverrides) *engine.RunOverrides {
296+
if o == nil {
297+
return nil
298+
}
299+
return &engine.RunOverrides{
300+
Model: o.Model,
301+
Temperature: o.Temperature,
302+
MaxSteps: o.MaxSteps,
303+
MaxTokens: o.MaxTokens,
304+
ReasoningLoop: o.ReasoningLoop,
305+
SystemPrompt: o.SystemPrompt,
306+
PersonaRef: o.PersonaRef,
307+
InlineSkills: o.InlineSkills,
308+
InlineTraits: o.InlineTraits,
309+
InlineBehaviors: o.InlineBehaviors,
310+
Tools: o.Tools,
311+
}
221312
}

api/api.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,8 @@ func (a *API) RegisterRoutes(router forge.Router) error {
5959
if err := a.registerMemoryRoutes(router); err != nil {
6060
return err
6161
}
62-
return a.registerToolRoutes(router)
62+
if err := a.registerToolRoutes(router); err != nil {
63+
return err
64+
}
65+
return a.registerConfigRoutes(router)
6366
}

api/behavior_handler.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
func (a *API) registerBehaviorRoutes(router forge.Router) error {
15-
g := router.Group("/cortex", forge.WithGroupTags("behaviors"))
15+
g := router.Group("", forge.WithGroupTags("behaviors"))
1616

1717
if err := g.POST("/behaviors", a.createBehavior,
1818
forge.WithSummary("Create behavior"),
@@ -117,7 +117,7 @@ func (a *API) getBehavior(ctx forge.Context, _ *GetBehaviorRequest) (*behavior.B
117117
return b, ctx.JSON(http.StatusOK, b)
118118
}
119119

120-
func (a *API) listBehaviors(ctx forge.Context, req *ListBehaviorsRequest) ([]*behavior.Behavior, error) {
120+
func (a *API) listBehaviors(ctx forge.Context, req *ListBehaviorsRequest) (*ListBehaviorsResponse, error) {
121121
behaviors, err := a.eng.ListBehaviors(ctx.Context(), &behavior.ListFilter{
122122
AppID: cortex.AppFromContext(ctx.Context()),
123123
Limit: defaultLimit(req.Limit),
@@ -126,7 +126,8 @@ func (a *API) listBehaviors(ctx forge.Context, req *ListBehaviorsRequest) ([]*be
126126
if err != nil {
127127
return nil, fmt.Errorf("list behaviors: %w", err)
128128
}
129-
return behaviors, ctx.JSON(http.StatusOK, behaviors)
129+
resp := &ListBehaviorsResponse{Items: behaviors}
130+
return resp, ctx.JSON(http.StatusOK, resp)
130131
}
131132

132133
func (a *API) updateBehavior(ctx forge.Context, req *UpdateBehaviorRequest) (*behavior.Behavior, error) {

api/checkpoint_handler.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func (a *API) registerCheckpointRoutes(router forge.Router) error {
14-
g := router.Group("/cortex", forge.WithGroupTags("checkpoints"))
14+
g := router.Group("", forge.WithGroupTags("checkpoints"))
1515

1616
if err := g.GET("/checkpoints", a.listCheckpoints,
1717
forge.WithSummary("List pending checkpoints"),
@@ -38,15 +38,16 @@ func (a *API) registerCheckpointRoutes(router forge.Router) error {
3838
return nil
3939
}
4040

41-
func (a *API) listCheckpoints(ctx forge.Context, req *ListCheckpointsRequest) ([]*checkpoint.Checkpoint, error) {
41+
func (a *API) listCheckpoints(ctx forge.Context, req *ListCheckpointsRequest) (*ListCheckpointsResponse, error) {
4242
cps, err := a.eng.ListPendingCheckpoints(ctx.Context(), &checkpoint.ListFilter{
4343
Limit: defaultLimit(req.Limit),
4444
Offset: req.Offset,
4545
})
4646
if err != nil {
4747
return nil, fmt.Errorf("list checkpoints: %w", err)
4848
}
49-
return cps, ctx.JSON(http.StatusOK, cps)
49+
resp := &ListCheckpointsResponse{Items: cps}
50+
return resp, ctx.JSON(http.StatusOK, resp)
5051
}
5152

5253
func (a *API) resolveCheckpoint(ctx forge.Context, req *ResolveCheckpointRequest) (*struct{}, error) {

api/config_handler.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package api
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/xraph/forge"
8+
9+
"github.com/xraph/cortex"
10+
)
11+
12+
// UpdateConfigRequest is the request body for updating engine configuration.
13+
type UpdateConfigRequest struct {
14+
DefaultModel string `json:"default_model,omitempty"`
15+
DefaultMaxSteps int `json:"default_max_steps,omitempty"`
16+
DefaultMaxTokens int `json:"default_max_tokens,omitempty"`
17+
DefaultTemperature float64 `json:"default_temperature,omitempty"`
18+
DefaultReasoningLoop string `json:"default_reasoning_loop,omitempty"`
19+
}
20+
21+
func (a *API) registerConfigRoutes(router forge.Router) error {
22+
g := router.Group("", forge.WithGroupTags("config"))
23+
24+
if err := g.GET("/config", a.getConfig,
25+
forge.WithSummary("Get config"),
26+
forge.WithDescription("Returns the current engine configuration."),
27+
forge.WithOperationID("getConfig"),
28+
forge.WithResponseSchema(http.StatusOK, "Engine configuration", &cortex.Config{}),
29+
forge.WithErrorResponses(),
30+
); err != nil {
31+
return fmt.Errorf("register config routes: %w", err)
32+
}
33+
34+
if err := g.PUT("/config", a.updateConfig,
35+
forge.WithSummary("Update config"),
36+
forge.WithDescription("Updates engine configuration at runtime. Changes are in-memory only."),
37+
forge.WithOperationID("updateConfig"),
38+
forge.WithRequestSchema(UpdateConfigRequest{}),
39+
forge.WithResponseSchema(http.StatusOK, "Updated configuration", &cortex.Config{}),
40+
forge.WithErrorResponses(),
41+
); err != nil {
42+
return fmt.Errorf("register config routes: %w", err)
43+
}
44+
45+
return nil
46+
}
47+
48+
func (a *API) getConfig(ctx forge.Context, _ *struct{}) (*cortex.Config, error) {
49+
cfg := a.eng.Config()
50+
return &cfg, ctx.JSON(http.StatusOK, cfg)
51+
}
52+
53+
func (a *API) updateConfig(ctx forge.Context, req *UpdateConfigRequest) (*cortex.Config, error) {
54+
update := cortex.Config{
55+
DefaultModel: req.DefaultModel,
56+
DefaultMaxSteps: req.DefaultMaxSteps,
57+
DefaultMaxTokens: req.DefaultMaxTokens,
58+
DefaultTemperature: req.DefaultTemperature,
59+
DefaultReasoningLoop: req.DefaultReasoningLoop,
60+
}
61+
62+
cfg := a.eng.UpdateConfig(update)
63+
return &cfg, ctx.JSON(http.StatusOK, cfg)
64+
}

api/memory_handler.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func (a *API) registerMemoryRoutes(router forge.Router) error {
14-
g := router.Group("/cortex", forge.WithGroupTags("memory"))
14+
g := router.Group("", forge.WithGroupTags("memory"))
1515

1616
if err := g.GET("/agents/:name/memory", a.getConversation,
1717
forge.WithSummary("Get conversation"),
@@ -37,7 +37,7 @@ func (a *API) registerMemoryRoutes(router forge.Router) error {
3737
return nil
3838
}
3939

40-
func (a *API) getConversation(ctx forge.Context, req *GetConversationRequest) ([]memory.Message, error) {
40+
func (a *API) getConversation(ctx forge.Context, req *GetConversationRequest) (*GetConversationResponse, error) {
4141
appID := cortex.AppFromContext(ctx.Context())
4242
cfg, err := a.eng.GetAgentByName(ctx.Context(), appID, ctx.Param("name"))
4343
if err != nil {
@@ -51,7 +51,8 @@ func (a *API) getConversation(ctx forge.Context, req *GetConversationRequest) ([
5151
if err != nil {
5252
return nil, fmt.Errorf("load conversation: %w", err)
5353
}
54-
return messages, ctx.JSON(http.StatusOK, messages)
54+
resp := &GetConversationResponse{Messages: messages}
55+
return resp, ctx.JSON(http.StatusOK, resp)
5556
}
5657

5758
func (a *API) clearConversation(ctx forge.Context, _ *ClearConversationRequest) (*struct{}, error) {

0 commit comments

Comments
 (0)