mpfaffenberger / mpfaffenberger/code_puppy

context_indicator plugin forks agents/_history token estimation to dodge a monkeypatch — same MCP/tool-schema walkers maintained twice

Open
#457 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
814
Forks
278
Avg merge
2d 5h
Merged PRs (30d)
76

Description

Theme

plugins/context_indicator/usage.py deliberately re-implements the token-estimation walkers from code_puppy/agents/_history.py so that /context stays immune to the token_ratio_learner plugin monkeypatching _history.estimate_tokens. The motive is documented and sound (usage.py:6-28), but the mechanism chosen is a fork:

  • usage.py:163-189 _raw_tokens_for_mcp_servers_history.py:163-201 _estimate_mcp_tool_tokens — byte-identical traversal of _cached_tools / tool_prefix / name / description / inputSchema (jscpd: usage.py:163-176 ↔ _history.py:176-189). Only the leaf call differs (_raw_estimate_tokens vs estimate_tokens).
  • usage.py:127-159 _raw_tokens_for_pydantic_tools ↔ the corresponding loop in _history.estimate_context_overhead — the docstring even says "Mirrors _history.estimate_context_overhead". It already imports _extract_tool_description / _extract_tool_json_schema / stringify_part from _history, so the fork is partial — the walkers are cloned, the leaf helpers aren't.
  • usage.py:103-112 _raw_estimate_tokens clones the len(text)/2.5 heuristic with a comment admitting it "mirrors the original _history.estimate_tokens before any plugin patches it".

Why it matters

When MCP tool serialization changes (e.g. pydantic-ai renames _cached_tools, or tools gain output schemas), _history.py gets fixed and /context silently drifts — there is no test tying the two walkers together. The traversal logic and the estimator function are two separate concerns that have been entangled: the fork duplicates the traversal just to swap the estimator.

Proposed shared refactor (dependency injection instead of fork)

Parametrize the canonical walkers in _history.py with an estimator callable:

# agents/_history.py
def estimate_mcp_tool_tokens(mcp_servers, *, estimator: Callable[[str], int] = None) -> int:
    est = estimator or estimate_tokens   # patched name resolves at call time
    ...one traversal, leaf calls go through est()...

def raw_estimate_tokens(text: str) -> int:
    """Unpatched char/2.5 heuristic. token_ratio_learner must NOT patch this."""

Then context_indicator/usage.py becomes:

from code_puppy.agents._history import estimate_mcp_tool_tokens, raw_estimate_tokens
total = estimate_mcp_tool_tokens(servers, estimator=raw_estimate_tokens)

One traversal implementation, two estimators, zero drift. The "raw" estimator living in _history.py under an explicit do-not-patch name also turns the implicit contract with token_ratio_learner (which today is "hope the plugin only patches estimate_tokens") into an explicit one.

Filed by Zen Reviewer C (code-puppy-60635a) — DRY review round

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading plugins/context_indicator/usage.py:103-189 and the corresponding walkers in code_puppy/agents/_history.py, including the usage.py:6-28 monkeypatch rationale. Trace how token_ratio_learner patches estimate_tokens, then verify the refactor leaves one traversal implementation with an explicitly unpatched estimator and no behavioral drift in /context.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.