Chip Mechanism Design Decisions#

1. Motivation#

In the MCP (Model Context Protocol) call chain, tool parameters generated by LLMs often fail due to JSON escaping issues, missing fields, or malformed formatting. Once a failure occurs, the original parameter content may only appear in a one-off error response, making it difficult for the model to reproduce the exact same content in subsequent rounds — especially for large code writes (be-write) or multi-line replacements.

The core goal of the Chip mechanism is to temporarily preserve the “failure scene” or “deleted content” as a reliable data source for subsequent model recovery operations. It acts as a lightweight FIFO cache queue maintained within the process, allowing the LLM to reinsert previously lost or deleted content into target files via the dedicated be-insert-chip tool, thereby reducing the probability of an entire task being interrupted due to a single call failure.

2. When a Chip Is Created#

There are currently two scenarios that trigger chip recording:

2.1 Automatic Parameter Saving on Tool Call Failure#

When a tool call returns an error and the serialized JSON length of the input parameters exceeds 50 bytes, the server calls SaveChip(tool, args, errMsg) to save the original parameters as a chip.

func SaveChip(tool string, args map[string]any, errMsg string) string
  • Parameters with JSON length ≤ 50 bytes are not saved, to avoid recording too many meaningless short parameters.
  • Returns a chip ID (e.g., a3f7b2) on success; returns an empty string if recovery is not needed.

2.2 Saving Deleted Content on Delete Operations#

be-delete saves non-empty deleted content as a chip via SaveContentChip before actually writing to disk, and prompts the model in the returned warnings:

deletedContent := strings.Join(fileLines[start-1:end], "")
if deletedContent != "" {
    chipID, chipWarn := SaveContentChip("be-delete", deletedContent)
    warnings = append(warnings, fmt.Sprintf("deleted content saved as chip://%s", chipID))
}

This allows reinserting accidentally deleted content back into the file via be-insert-chip.

3. What Is Stored in a Chip#

The chip data structure is defined in pkg/betools/chip.go:

type ChipRecord struct {
    ID        string         `json:"id"`        // chip unique identifier
    Tool      string         `json:"tool"`      // source tool name, e.g. be-write / be-delete
    Args      map[string]any `json:"args"`      // original parameters or deleted content
    ErrMsg    string         `json:"err_msg,omitempty"` // error message on failure
    CreatedAt int64          `json:"created_at"`        // creation timestamp (Unix seconds)
}
  • Chip from failed calls: Args stores the original tool parameters, ErrMsg stores the error text.
  • Chip from delete operations: Args is {"_content": "<deleted text>"}, ErrMsg is empty.

The chip ID is generated by newShortID, defaulting to a 6-character hex random string (3 bytes of entropy). On collision, it retries up to 5 times, and falls back to a 12-character hex string if conflicts persist.

4. How to List, Read, and Use Chips#

4.1 Listing Chips#

When calling be-insert-chip without from and to, the server returns all chip IDs currently in memory:

{
  "status": "ok",
  "chips": ["a3f7b2", "c8e101", "d245aa"]
}

Internally implemented via ListChips(), returning in FIFO order (oldest first).

4.2 Reading a Single Chip#

A chip can be read by ID via GetChip(id). It searches the in-memory queue first; if not found in memory (e.g., after a process restart), it falls back to reading chip-{id}.json from disk.

4.3 Replaying Chip Content#

be-insert-chip supports two sources:

  • file:///absolute/path: reads content from a specified file.
  • chip://{id}: reads content from a chip cache.

Target location format:

  • to: file:///absolute/path:line_number

When the source is chip://, the server re-serializes the chip’s Args as JSON, prepends a comment header, and inserts the content into the target file via betools.Insert:

content = fmt.Sprintf("// Chip %s from tool %q\n// Original arguments:\n%s", rec.ID, rec.Tool, string(argsJSON))

This allows the model to clearly see in the diff which failed call’s content is being replayed.

5. Queue Capacity, Eviction Strategy, and Persistence#

5.1 Capacity and Eviction#

const maxChips = 30
  • The chip queue is globally unique, protected by sync.Mutex.
  • When the queue exceeds 30, the oldest chip is evicted (FIFO).
  • Evicted chips are removed from the in-memory chipStore and chipIDSet, and the corresponding disk file is deleted.
  • SaveContentChip returns a warning text on eviction, for example:
    oldest chip a3f7b2 was evicted (queue max 30)

5.2 Disk Persistence#

Each chip is written independently to a JSON file:

path := filepath.Join(ChipDir(), fmt.Sprintf("chip-%s.json", record.ID))

The cache directory is platform-dependent:

  • Windows: %LOCALAPPDATA%/better-edit-tools-mcp/chips
  • Linux/macOS: $XDG_CACHE_HOME/better-edit-tools-mcp/chips or ~/.cache/better-edit-tools-mcp/chips
  • Fallback: /tmp/better-edit-tools-mcp-chips

On process startup, loadChipsFromDisk() restores chips from this directory:

  1. Read all .json files;
  2. Sort by CreatedAt;
  3. Evict old files if exceeding maxChips;
  4. Load into the in-memory queue.

Both writing and deletion are best-effort — disk IO failures do not interrupt the main flow.

6. Current Limitations and Future Directions#

6.1 Current Limitations#

  1. Fixed capacity: maxChips is a compile-time constant of 30, cannot be dynamically adjusted per session or disk space.
  2. No TTL: Only FIFO eviction, no time-based expiration mechanism.
  3. Text only: Chip content is stored as strings, not suitable for binary file content.
  4. Small parameters not saved: Parameters with JSON ≤ 50 bytes do not generate a chip; some short but critical parameters may be lost.
  5. Unreliable disk persistence: Write failures are silently ignored; process crashes may leave residual or missing chips.
  6. Limited cross-process consistency: Although chips are read from disk, multiple server instances running concurrently may overwrite each other’s files.
  7. Fixed replay format: chip:// sources always prepend a comment header, which may be undesirable in some scenarios.

6.2 Future Directions#

  • Configurable capacity: Adjust maxChips via startup parameters or environment variables.
  • TTL / expiration strategy: Add expiration times to chips, automatically clean up stale records.
  • Categorization and search: Group by tool type, file path, or error keywords, making it easier for the model to quickly locate chips that need recovery.
  • Snapshot integration: Combine chips with transaction snapshots, supporting “rollback to pre-delete state” with automatic chip attachment.
  • Direct parameter replay: In addition to inserting Args as text, provide a recovery mode that “re-calls a tool with the original parameters”.
  • Encryption or signing: Encrypt or checksum sensitive file content persisted to shared cache directories, preventing information leakage or tampering.
  • Friendlier list view: Return metadata such as tool name, creation time, and content summary in ListChips, rather than just IDs.

Related source files: pkg/betools/chip.go, pkg/betools/id.go, pkg/betools/ops.go, internal/server/server.go