# How Dexter Deduplicates Skills and Enforces Tool-Call Limits Before Execution

> Dexter deduplicates skills and enforces tool call limits pre-execution. Learn how virattt/dexter ensures efficient and controlled tool usage with in-memory checks for agents and scratchpads.

- Repository: [Virat Singh/dexter](https://github.com/virattt/dexter)
- Tags: internals
- Published: 2026-02-16

---

**Dexter prevents duplicate skill execution and monitors tool usage through in-memory checks in `AgentToolExecutor` and `Scratchpad` before any external API calls are made.**

In the `virattt/dexter` repository, the agent loop applies two critical safety mechanisms to ensure efficient execution: skill deduplication and tool-call limit checking. These checks occur in `AgentToolExecutor` before any external tool is invoked, preventing redundant workflow runs and alerting the LLM when approaching usage thresholds.

## Skill Deduplication Mechanism

### Detection in AgentToolExecutor

The deduplication logic begins in [`src/agent/tool-executor.ts`](https://github.com/virattt/dexter/blob/main/src/agent/tool-executor.ts) within the `executeAll` method. When iterating over the LLM's `tool_calls`, the executor specifically checks for the `skill` tool:

```typescript
// src/agent/tool-executor.ts
if (toolName === 'skill') {
  const skillName = toolArgs.skill as string;
  if (ctx.scratchpad.hasExecutedSkill(skillName)) continue; // ← skip duplicate
}

```

### Tracking in Scratchpad

The `Scratchpad` class in [`src/agent/scratchpad.ts`](https://github.com/virattt/dexter/blob/main/src/agent/scratchpad.ts) maintains an in-memory JSON-L log of all executions. The `hasExecutedSkill` method searches for existing `tool_result` entries with type `skill`:

```typescript
// src/agent/scratchpad.ts
hasExecutedSkill(skillName: string): boolean {
  return this.readEntries().some(
    e => e.type === 'tool_result' && e.toolName === 'skill' && e.args?.skill === skillName
  );
}

```

When a skill completes, `addToolResult` records the execution, ensuring subsequent calls are detected as duplicates and skipped before any external API request occurs.

## Tool-Call Limit Enforcement

### Query Extraction and Limit Checking

Before executing any tool, `AgentToolExecutor.executeSingle` extracts a representative query string from the tool arguments and consults the scratchpad:

```typescript
const toolQuery = this.extractQueryFromArgs(toolArgs);
const limitCheck = ctx.scratchpad.canCallTool(toolName, toolQuery);

```

The `canCallTool` method in [`src/agent/scratchpad.ts`](https://github.com/virattt/dexter/blob/main/src/agent/scratchpad.ts) evaluates three conditions against the configured `maxCallsPerTool` limit and `similarityThreshold`:

1. **Call count vs. maximum** – warns if the count already reached the ceiling
2. **Query similarity** – uses Jaccard similarity to detect near-duplicate queries (similarity ≥ threshold triggers warning)
3. **Approaching limit** – warns when only one call remains

### Advisory Warnings and Event Emission

Unlike hard limits, Dexter's restrictions are advisory. The `canCallTool` method always returns `{ allowed: true, warning?: string }`, never blocking execution. When a warning exists, the executor emits a `tool_limit` event before the actual `tool_start`:

```typescript
if (limitCheck.warning) {
  yield {
    type: 'tool_limit',
    tool: toolName,
    warning: limitCheck.warning,
    blocked: false,
  };
}

```

This event allows the LLM to adapt its strategy—such as switching tools or rephrasing queries—while the system continues to track usage via `recordToolCall` after successful execution.

## Summary

- **Skill deduplication** occurs in `AgentToolExecutor.executeAll` using `Scratchpad.hasExecutedSkill` to ensure each SKILL.md workflow runs only once per user query.
- **Tool-call limits** are checked in `AgentToolExecutor.executeSingle` via `Scratchpad.canCallTool`, which tracks counts and query similarity using Jaccard comparison.
- Both mechanisms operate **before** external API calls, using in-memory JSON-L logs stored in the scratchpad for fast, deterministic checks.
- Limits are **advisory only**—execution proceeds but emits `tool_limit` events to inform the LLM of potential inefficiencies or near-duplicates.

## Frequently Asked Questions

### Does Dexter block execution when tool limits are reached?

No. Dexter treats tool limits as advisory rather than hard constraints. When `Scratchpad.canCallTool` detects that `maxCallsPerTool` has been reached or that a query is too similar to previous calls, it returns a warning but sets `allowed: true`. The executor emits a `tool_limit` event to notify the LLM, allowing it to adjust strategy while permitting the call to proceed.

### How does Dexter detect duplicate skill invocations?

Dexter checks for duplicate skills using the `hasExecutedSkill` method in [`src/agent/scratchpad.ts`](https://github.com/virattt/dexter/blob/main/src/agent/scratchpad.ts). This method scans the in-memory JSON-L log for existing `tool_result` entries where `toolName` equals `"skill"` and the `skill` argument matches the requested workflow. If found, `AgentToolExecutor.executeAll` skips the duplicate call via a `continue` statement before any external execution occurs.

### What similarity metric does Dexter use for query deduplication?

Dexter uses **Jaccard similarity** to compare the current tool query against previous queries for the same tool. The `canCallTool` method tokenizes the query strings and calculates the intersection over union ratio. If the similarity meets or exceeds the configured `similarityThreshold`, the method returns a warning indicating a near-duplicate query, helping prevent redundant API calls to search or data providers.

### Where are skill and tool execution records stored?

All execution records are stored in the `Scratchpad` class within [`src/agent/scratchpad.ts`](https://github.com/virattt/dexter/blob/main/src/agent/scratchpad.ts). The scratchpad maintains an in-memory JSON-L (JSON Lines) log where each entry represents an event such as `tool_result` or `tool_limit`. This design ensures fast, deterministic lookups for deduplication and limit checking without requiring external database queries or persistent storage during the agent loop.