vxcontrol/pentagi · error

failed to unmarshal search in memory action arguments

Error message

failed to unmarshal %s search in memory action arguments: %w

What it means

The `search in memory` tool received tool-call arguments that could not be decoded into the `SearchInMemoryAction` struct. The LLM agent produced JSON that fails `json.Unmarshal` (malformed JSON, wrong types, or unknown/misspelled fields with strict typing). This library wraps the underlying decode error so the caller sees both the tool name and the JSON error.

Solutions

  1. Log and inspect the raw `args` string next to the wrapped error to see exactly what the model produced.
  2. Verify the tool's JSON schema advertised to the LLM matches the current `SearchInMemoryAction` struct fields and types.
  3. Check the inner `%w` error (e.g. `json: cannot unmarshal string into Go struct field ...`) to pinpoint the offending field.
  4. Retry the tool call; if a specific model repeatedly mis-formats arguments, switch provider or strengthen the tool description.
  5. If arguments come from your own code, marshal with `json.Marshal(SearchInMemoryAction{...})` instead of hand-built strings.

Example fix

// before
args := []byte(`{"questions": "what is x"}`) // questions must be an array
// after
args, _ := json.Marshal(SearchInMemoryAction{Questions: []string{"what is x"}})
Defensive patterns

Strategy: validation

Validate before calling

var probe map[string]any
if err := json.Unmarshal(args, &probe); err != nil {
    return fmt.Errorf("invalid tool args JSON: %w", err)
}
q, ok := probe["questions"].([]any)
if !ok || len(q) == 0 {
    return errors.New("questions must be a non-empty array of strings")
}

Type guard

func isValidSearchInMemoryArgs(args []byte) bool {
    var a struct{ Questions []string `json:"questions"` }
    return json.Unmarshal(args, &a) == nil && len(a.Questions) > 0
}

Try / catch

out, err := mem.Handle(ctx, tools.SearchInMemoryToolName, args)
if err != nil {
    var jsonErr *json.UnmarshalTypeError
    if errors.As(err, &jsonErr) {
        logger.Warnf("malformed tool args: %v, raw=%s", err, string(args))
        return retryToolCall(ctx) // ask the model to re-emit arguments
    }
    return err
}

Prevention

When it happens

Trigger: An agent invokes the `search_in_memory` tool via `memory.Handle(ctx, name, args)` with `name == SearchInMemoryToolName` and `args` bytes that json.Unmarshal cannot decode into `SearchInMemoryAction` — e.g. `questions` sent as a string instead of an array, truncated JSON, or a non-string `query` field.

Common situations: LLM providers emitting malformed tool-call JSON; prompt templates that show an outdated action schema; models wrapping arguments in markdown fences or extra nesting; a struct field type changed (e.g. `Questions []string` → string) while the model still emits the old shape.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/8edd226745ce694a. Report an issue: GitHub.

Appendix: source

Thrown at backend/pkg/tools/memory.go:63

	}

	ctx, observation := obs.Observer.NewObservation(ctx)
	logger := logrus.WithContext(ctx).WithFields(enrichLogrusFields(m.flowID, nil, nil, logrus.Fields{
		"tool": name,
		"args": string(args),
	}))

	if m.store == nil {
		logger.Error("pgvector store is not initialized")
		return "", fmt.Errorf("pgvector store is not initialized")
	}

	switch name {
	case SearchInMemoryToolName:
		var action SearchInMemoryAction
		if err := json.Unmarshal(args, &action); err != nil {
			logger.WithError(err).Error("failed to unmarshal search in memory action arguments")
			return "", fmt.Errorf("failed to unmarshal %s search in memory action arguments: %w", name, err)
		}

		filters := map[string]any{
			"flow_id":  strconv.FormatInt(m.flowID, 10),
			"doc_type": memoryVectorStoreDefaultType,
		}
		if action.TaskID != nil && *action.TaskID != 0 {
			filters["task_id"] = action.TaskID.String()
		}
		if action.SubtaskID != nil && *action.SubtaskID != 0 {
			filters["subtask_id"] = action.SubtaskID.String()
		}

		isSpecificFilters, globalFilters := getGlobalFilters(filters)
		metadata := langfuse.Metadata{
			"tool_name":        name,
			"message":          action.Message,
			"limit":            memoryVectorStoreResultLimit,

View on GitHub (pinned to ea665308ba)