diff --git a/.env.example b/.env.example index 5b6f3a8..2573271 100644 --- a/.env.example +++ b/.env.example @@ -14,4 +14,4 @@ AGENT_PROFILE_PATH=profiles/kaya.yaml HEALTH_LISTEN_ADDR=:8087 METRICS_LISTEN_ADDR=:9097 SHUTDOWN_TIMEOUT=15s -RESPONSE_TIMEOUT=120s +RESPONSE_TIMEOUT=180s diff --git a/README.md b/README.md index 5796b03..77d894b 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Long-lived daemon: 2. On `comment.added` where mentions include agent `kaya` and author ≠ `kaya`: - dedupes by comment id - GET task + comments via REST - - calls `l-ai-core` `AIService.RunAgent` (tools disabled) with Kaya persona - - POST comment with `Idempotency-Key: mention-reply:` + - calls `l-ai-core` `AIService.RunAgent` with tools enabled (`tool_profile=kaya-board`: journal, tasks, steam/faceit, discord, node — same surface as Telegram chat; no gitea/evolver) + - POST comment with `Idempotency-Key: mention-reply:` (do not use `add_comment` for the reply itself) No Telegram / Live Chat integration. @@ -35,6 +35,7 @@ No Telegram / Live Chat integration. | `AGENT_PROFILE_PATH` | no | `profiles/kaya.yaml` | | `HEALTH_LISTEN_ADDR` | no | `:8087` | | `METRICS_LISTEN_ADDR` | no | `:9097` | +| `RESPONSE_TIMEOUT` | no | `180s` | ## Build diff --git a/internal/config/config.go b/internal/config/config.go index 9d18168..7cb7aec 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -35,7 +35,7 @@ func Load() (Config, error) { MetricsListenAddr: env("METRICS_LISTEN_ADDR", ":9097"), HealthListenAddr: env("HEALTH_LISTEN_ADDR", ":8087"), ShutdownTimeout: envDur("SHUTDOWN_TIMEOUT", 15*time.Second), - ResponseTimeout: envDur("RESPONSE_TIMEOUT", 120*time.Second), + ResponseTimeout: envDur("RESPONSE_TIMEOUT", 180*time.Second), } if c.TasksWSURL == "" { return c, errors.New("TASKS_WS_URL is required") diff --git a/internal/inference/core.go b/internal/inference/core.go index 3202edc..fc4a549 100644 --- a/internal/inference/core.go +++ b/internal/inference/core.go @@ -59,17 +59,25 @@ func (r *CoreRunner) RunAgent(ctx context.Context, req RunRequest) (RunResult, e } toolsEnabled := req.ToolsEnabled - maxIters := uint32(1) - maxCalls := uint32(0) - if toolsEnabled { - maxIters = 12 - maxCalls = 20 + maxIters := req.MaxToolIterations + maxCalls := req.MaxTotalToolCalls + if !toolsEnabled { + maxIters = 1 + maxCalls = 0 + } else { + if maxIters == 0 { + maxIters = 12 + } + if maxCalls == 0 { + maxCalls = 20 + } } resp, err := r.api.RunAgent(r.auth(ctx), &corepb.AgentRequest{ - Messages: msgs, - ModelName: req.Model, - AgentId: req.AgentID, + Messages: msgs, + ModelName: req.Model, + AgentId: req.AgentID, + ToolProfile: req.ToolProfile, ToolLoop: &corepb.AgentToolLoopOptions{ ToolsEnabled: toolsEnabled, MaxToolIterations: maxIters, diff --git a/internal/inference/types.go b/internal/inference/types.go index d22c4fb..cfe765b 100644 --- a/internal/inference/types.go +++ b/internal/inference/types.go @@ -8,16 +8,19 @@ type ChatMessage struct { } type RunRequest struct { - Messages []ChatMessage - Model string - AgentID string - RequestID string - IdempotencyKey string - JSONSchema string - Repair bool - TimeoutSeconds uint32 - Metadata map[string]string - ToolsEnabled bool + Messages []ChatMessage + Model string + AgentID string + RequestID string + IdempotencyKey string + JSONSchema string + Repair bool + TimeoutSeconds uint32 + Metadata map[string]string + ToolsEnabled bool + ToolProfile string + MaxToolIterations uint32 + MaxTotalToolCalls uint32 } type RunResult struct { diff --git a/internal/profile/profile.go b/internal/profile/profile.go index e407f49..0aac3de 100644 --- a/internal/profile/profile.go +++ b/internal/profile/profile.go @@ -101,8 +101,9 @@ func (p Profile) SystemPrompt() string { } b.WriteString("You reply only to task comment @mentions. Be helpful and concise. ") b.WriteString("Match the language of the triggering comment (Russian or English). ") - b.WriteString("When asked to journal / diary a merged change: write a short factual note of what shipped and any PR URL from the thread. ") - b.WriteString("Do not invent task state that is not in the provided context. ") + b.WriteString("Tools match Telegram chat (journal, tasks, steam/faceit, discord, node) — use them when needed. ") + b.WriteString("When asked to journal / diary a merged change: call write_journal (persona Postgres diary), then a short board reply noting what shipped and any PR URL. ") + b.WriteString("Do not invent task state that is not in the provided context or tool results. ") b.WriteString("Output only the comment body to post — no preamble or quotes.\n") return b.String() } diff --git a/internal/tasksboard/loop.go b/internal/tasksboard/loop.go index 3429708..dac9166 100644 --- a/internal/tasksboard/loop.go +++ b/internal/tasksboard/loop.go @@ -154,7 +154,8 @@ func (l *Loop) handle(ctx context.Context, ev tasksws.Event) error { sys := l.Profile.SystemPrompt() sys += " Avoid @mentioning other agents unless you need a concrete action — never @mention just to acknowledge." - sys += " If Mira asks you to journal a merged task, write a short diary-style board comment (what changed + PR if present)." + sys += " CRITICAL: Do NOT call add_comment — this process posts your reply text as the board comment." + sys += " If asked to journal / diary a merged task: call write_journal with a short factual entry (what shipped + PR URL if present), then still write a brief board reply." if authorType == "human" { sys += " A human mentioned you — always give a real reply." } @@ -164,13 +165,16 @@ func (l *Loop) handle(ctx context.Context, ev tasksws.Event) error { {Role: "system", Content: sys}, {Role: "user", Content: userPrompt}, }, - Model: model, - AgentID: agentID, - RequestID: "mention-reply:" + commentID, - IdempotencyKey: "mention-reply:" + commentID, - TimeoutSeconds: timeoutSec, - Metadata: map[string]string{"trace_reason": "kaya_mention_reply", "comment_id": commentID, "task_id": taskID}, - ToolsEnabled: false, + Model: model, + AgentID: agentID, + RequestID: "mention-reply:" + commentID, + IdempotencyKey: "mention-reply:" + commentID, + TimeoutSeconds: timeoutSec, + Metadata: map[string]string{"trace_reason": "kaya_mention_reply", "comment_id": commentID, "task_id": taskID}, + ToolsEnabled: true, + ToolProfile: "kaya-board", + MaxToolIterations: 10, + MaxTotalToolCalls: 18, }) if err != nil { return fmt.Errorf("RunAgent: %w", err) @@ -184,7 +188,7 @@ func (l *Loop) handle(ctx context.Context, ev tasksws.Event) error { if err != nil { return fmt.Errorf("post comment: %w", err) } - l.log().Info("mention reply posted", "comment_id", commentID, "task_id", taskID, "duration_ms", res.DurationMS) + l.log().Info("mention reply posted", "comment_id", commentID, "task_id", taskID, "duration_ms", res.DurationMS, "tools_used", res.ToolsUsed) return nil } @@ -207,7 +211,7 @@ func (l *Loop) shouldReplyToAgentMention(ctx context.Context, task Task, mention model = "deepseek-v4-flash" } user := "Task: " + task.Title + "\nTrigger comment from another agent:\n" + trimRunes(mentionBody, 800) + - "\n\nReply true ONLY if you must add new substance (journal entry Mira asked for). Default false for thanks/repeats." + "\n\nReply true if you must act (e.g. Mira asked you to journal via write_journal, or you need tools). Default false for thanks/acks/repeats." res, err := l.Core.RunAgent(ctx, inference.RunRequest{ Messages: []inference.ChatMessage{ {Role: "system", Content: "You gate Kaya's board replies to agent @mentions. Prefer false. Output JSON only."},