← Back to Blog
Papi has an RTK adapter

Papi has an RTK adapter

Compressing local commands before the model reads them.

When a Papi agent runs commands on the machine and feeds the results back to the model, most of what comes back is noise: a grep across a repository, a git status, a test run. Every line of it costs tokens and slows the next turn. Cutting the noise before the model sees it saves both.

RTK does exactly that: a CLI proxy that compresses command output while keeping the signal. papi-ai/rtk wraps it for PHP and implements Papi's LLMTokenOptimisationProxyInterface, so any app where a model drives local commands can use it without changing how the commands are run.

brew install rtk
composer require papi-ai/rtk

The rtk binary is a runtime requirement on PATH, not a Composer dependency. Pass a path to the constructor if it lives elsewhere.

Two entry points

optimiseCommand() runs a command through RTK's per-command filter, which knows what a git status or a grep looks like and compresses accordingly:

use PapiAI\Rtk\RtkProxy;

$rtk = new RtkProxy();

$result = $rtk->optimiseCommand('grep -rn "function " src/');

echo $result->optimised;
echo $result->savingsPercent();

To measure the saving it runs the command twice, once raw and once through rtk, so only pass commands without side effects.

optimise() takes text you already captured and pipes it through, with a named filter so RTK knows what it is looking at:

$result = $rtk->optimise($gitLogOutput, ['filter' => 'git-log']);
$result = $rtk->optimise($grepOutput, ['filter' => 'grep', 'ultraCompact' => true]);

What it actually saves

RTK's own rtk gain reports 60 to 90% across a working session. Per command, on papi-core:

git status25 to 13 tokens48%
grep -rn "function " src/5,172 to 3,999 tokens23%
git log --oneline -30441 to 441 tokens0%

--oneline is already terse, so RTK passes it through unchanged. The gain is on output that was verbose to begin with, which is where an agent spends its tokens.

Lossy, so it tells you

Compression that drops padding can also drop something you needed. Every call returns an OptimisationResult with the strategy used and the before and after counts:

$result->optimised;         // the compressed text
$result->tokensBefore;      // 5172
$result->tokensAfter;       // 3999
$result->tokensSaved();     // 1173
$result->savingsPercent();  // 22.7
$result->strategy;          // 'rtk:command'

A command RTK does not support throws a RuntimeException carrying RTK's own error, and so does a run that returns nothing for input that had something. You never get a saving RTK did not make.

Counting tokens without a tokenizer

The before and after figures come from TokenEstimatorInterface, which the proxy interface extends. The zero-dependency default, HeuristicTokenEstimator, counts bytes divided by four. That is deliberate: it errs high, and erring high is the safe direction for anything that has to fit inside a budget. Inject a real tokenizer if you have one; anything that only needs to size a payload can depend on the narrower interface alone.

Documentation · GitHub · Book