fix: send profileArn for social accounts, improve error messages

- Remove IsSocial guard from sqliteKiroAuth.ProfileARN(); the Kiro API
  now requires profileArn on all requests including personal accounts
- Add OLLIE_KIRO_PROFILE_ARN env var for static token auth
- Add repeated error detection (3x identical error triggers corrective nudge)
- Include expected format in tool dispatch error messages
This commit is contained in:
ollie 2026-07-11 16:08:44 +02:00
parent 75f137dda5
commit a50149d075
3 changed files with 48 additions and 11 deletions

View File

@ -76,6 +76,8 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
var roundsWithoutPlan int // consecutive tool rounds without a PLAN: block
var lastAction string // previous round's LastAction for stall detection
var stallRounds int // consecutive rounds with unchanged LastAction
var lastErrorSig string // signature of previous round's error(s) for repeat detection
var repeatErrorCount int // consecutive rounds with identical error signature
// resultCache stores outputs of read-safe tool calls keyed by name+args.
// Only tools classified as parallel-safe (immutable reads) are cached.
// sync.Map is required because execOne may run in concurrent goroutines.
@ -488,6 +490,36 @@ func run(ctx context.Context, cfg agentConfig, state state) error {
}, nil)
}
// Repeated identical error detection: if the same error occurs 3 times
// in a row, the model is stuck on a format/argument issue. Inject the
// error content as a corrective nudge immediately rather than waiting
// for the generic soft limit.
if allErrors && len(results) > 0 {
var errSig strings.Builder
for _, r := range results {
if r.IsError {
errSig.WriteString(r.Content)
errSig.WriteByte('\n')
}
}
sig := errSig.String()
if sig == lastErrorSig {
repeatErrorCount++
} else {
repeatErrorCount = 1
lastErrorSig = sig
}
if repeatErrorCount == 3 {
state.update(backend.Message{
Role: "user",
Content: "[system: you have made the same malformed tool call 3 times in a row. Read the error message carefully and fix the arguments. The error was: " + strings.TrimSpace(results[0].Content) + "]",
}, nil)
}
} else {
repeatErrorCount = 0
lastErrorSig = ""
}
// Replan gate: if the model has been calling tools without emitting a
// PLAN: block, force it to replan before continuing.
if strings.Contains(msg.Content, "PLAN:") {

View File

@ -646,14 +646,20 @@ type kiroAuthSource interface {
// staticKiroAuth holds a pre-issued bearer token.
type staticKiroAuth struct {
token string
token string
profileARN string
}
func (s *staticKiroAuth) DefaultEndpoint(_ context.Context) (string, error) {
if s.profileARN != "" {
return kiroDefaultEndpoint(s.profileARN)
}
return "", fmt.Errorf("base URL not configured and no profile ARN available with static auth")
}
func (s *staticKiroAuth) ProfileARN(_ context.Context) (string, error) { return "", nil }
func (s *staticKiroAuth) ProfileARN(_ context.Context) (string, error) {
return s.profileARN, nil
}
func (s *staticKiroAuth) AccessToken(_ context.Context) (string, error) {
t := strings.TrimSpace(s.token)
@ -712,10 +718,6 @@ func (s *sqliteKiroAuth) ProfileARN(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
// Personal/social accounts don't send profileArn in requests.
if state.IsSocial {
return "", nil
}
return strings.TrimSpace(state.ProfileARN), nil
}
@ -1377,7 +1379,10 @@ func kiroUpdateSSOCacheToken(path, accessToken, refreshToken, expiresAt string)
func newKiroAuthSource(apiKey string) (kiroAuthSource, error) {
apiKey = strings.TrimSpace(apiKey)
if !strings.HasPrefix(strings.ToLower(apiKey), "sqlite://") {
return &staticKiroAuth{token: apiKey}, nil
return &staticKiroAuth{
token: apiKey,
profileARN: strings.TrimSpace(os.Getenv("OLLIE_KIRO_PROFILE_ARN")),
}, nil
}
// Parse sqlite:// URI
parsed, err := url.Parse(apiKey)

View File

@ -193,7 +193,7 @@ func dispatchExecuteCode(ctx context.Context, e *Server, args json.RawMessage) (
return "", fmt.Errorf("execute_code: bad args: %w", err)
}
if len(steps) == 0 {
return "", fmt.Errorf("execute_code: steps is required")
return "", fmt.Errorf("execute_code: steps is required. Format: {\"steps\": [{\"code\": \"your code here\"}]}")
}
return e.dispatchSteps(ctx, steps, timeout, sandboxName)
}
@ -203,10 +203,10 @@ func dispatchExecuteCode(ctx context.Context, e *Server, args json.RawMessage) (
func dispatchCallTool(ctx context.Context, e *Server, args json.RawMessage) (string, error) {
calls, timeout, sandboxName, err := execCallToolArgs(args)
if err != nil {
return "", fmt.Errorf("call_tool: bad args: %w", err)
return "", fmt.Errorf("call_tool: bad args: %w. Expected format: {\"calls\": [{\"tool\": \"tool_name\", \"args\": [\"arg1\", ...]}]}", err)
}
if len(calls) == 0 {
return "", fmt.Errorf("call_tool: calls is required")
return "", fmt.Errorf("call_tool: calls is required. Format: {\"calls\": [{\"tool\": \"tool_name\", \"args\": [\"arg1\", ...]}]}")
}
// Validate: every top-level call must name a tool (or be a parallel group of tools).
for i, c := range calls {
@ -238,7 +238,7 @@ func dispatchPipeCall(ctx context.Context, e *Server, args json.RawMessage) (str
return "", fmt.Errorf("pipe: bad args: %w", err)
}
if len(stages) == 0 {
return "", fmt.Errorf("pipe: stages is required")
return "", fmt.Errorf("pipe: stages is required. Format: {\"stages\": [{\"code\": \"...\"} or {\"tool\": \"name\", \"args\": [...]}]}")
}
return e.dispatchPipe(ctx, stages, timeout, sandboxName)
}