It appears that the problem was with routing setting default parameters. In the API controller, params[:format] was 'json' and in the Admin controller it was nil.
When I added this to my Admin controller:
params[:format] = 'json'
expire_action(:controller => 'api/books', :action => 'show', :id => @book.id, :format => 'json')
Then, the fragments in both controllers matched.
This let me back to my routes:
In my routes I had this:
namespace :api, :defaults => {:format => 'json'} do
resources :books, :only => [:show]
end
This caused params[:format] to be set in my api namespace. I didn't have anything in my admin namespace and apparently actionpack/ActionCachePath saw these two differently. Removing the defaults in the routes fixed the problem.