Summary
In MCP mode, state.Metrics.{TokensIn, TokensOut, CostUSD, Turns} are never incremented from real agent activity. Only ToolCalls and FilesRead/Written update. Consequence: any limits entry with enforcement: "fail-fast" (paper §4) that depends on token or spend counters is effectively a no-op.
Evidence
Live test session (aflock-mcp-test, 2 tool calls executed):
// aflock get_session output:
{
"costUSD": 0,
"filesRead": 1,
"filesWritten": 0,
"tokensIn": 0,
"tokensOut": 0,
"toolCalls": 2,
"turns": 0
}
After ~5 real Claude round-trips consuming ~10k tokens total, metrics still read zero.
Root cause
Only two code paths populate those fields:
internal/state/session.go:239 UpdateMetrics() — takes explicit tokensIn, tokensOut, costUSD
internal/state/session.go:249 IncrementTurns()
In hooks mode, internal/hooks/handler.go reads usage/cost fields from the PostToolUse hook input JSON and calls those functions. In MCP mode, there is no equivalent — aflock is not on the Anthropic API path, so it has no visibility into tokens/cost. No MCP handler ever calls UpdateMetrics or IncrementTurns.
Why this matters
Paper §4 (Resource Limits):
fail-fast: Abort immediately when breached (for cost, security)
With zero metrics, fail-fast limits never trip. An adversarial or runaway agent can exceed maxSpendUSD: 1.00 arbitrarily without aflock noticing — until aflock verify --session is run post-hoc, by which point the spend has already happened.
The limits:post-hoc check in verify also reports metrics as zero because the state file itself is zero, so even post-hoc enforcement doesn't work in MCP mode.
Options
-
Hybrid mode: keep hooks installed purely for metrics, even when the authorization path is MCP. Requires PostToolUse hook to write into the same session state MCP uses. Works today but defeats the "MCP-only" deployment story.
-
Client-supplied usage via _usage tool arg: extend each MCP tool handler (handleBash, etc.) to accept _tokens_in, _tokens_out, _cost_usd and require the client to supply them. Claude Code does not currently populate these — would need upstream work.
-
Anthropic SDK proxy: the aflock MCP server could proxy Anthropic API calls (or wrap the SDK) so it sees usage responses. Large scope change; probably overkill.
-
Documented gap: update paper and docs to say fail-fast limits only work in hooks mode; MCP supports post-hoc only.
Recommendation: (1) as a short-term workaround + (4) in docs, with (2) as a proper fix tracked for later.
Repro
# Policy with an aggressive limit
echo '{"version":"1.0","name":"metrics-test","limits":{"maxTokensIn":{"value":1,"enforcement":"fail-fast"}},"tools":{"allow":["Bash"]}}' > .aflock
# Start Claude with aflock MCP
# Make 10 bash calls via aflock bash
# Observe: all 10 succeed, session.Metrics.TokensIn stays 0
Related
Summary
In MCP mode,
state.Metrics.{TokensIn, TokensOut, CostUSD, Turns}are never incremented from real agent activity. OnlyToolCallsandFilesRead/Writtenupdate. Consequence: anylimitsentry withenforcement: "fail-fast"(paper §4) that depends on token or spend counters is effectively a no-op.Evidence
Live test session (
aflock-mcp-test, 2 tool calls executed):After ~5 real Claude round-trips consuming ~10k tokens total, metrics still read zero.
Root cause
Only two code paths populate those fields:
internal/state/session.go:239 UpdateMetrics()— takes explicittokensIn, tokensOut, costUSDinternal/state/session.go:249 IncrementTurns()In hooks mode,
internal/hooks/handler.goreadsusage/costfields from thePostToolUsehook input JSON and calls those functions. In MCP mode, there is no equivalent — aflock is not on the Anthropic API path, so it has no visibility into tokens/cost. No MCP handler ever callsUpdateMetricsorIncrementTurns.Why this matters
Paper §4 (Resource Limits):
With zero metrics,
fail-fastlimits never trip. An adversarial or runaway agent can exceedmaxSpendUSD: 1.00arbitrarily without aflock noticing — untilaflock verify --sessionis run post-hoc, by which point the spend has already happened.The
limits:post-hoccheck inverifyalso reports metrics as zero because the state file itself is zero, so even post-hoc enforcement doesn't work in MCP mode.Options
Hybrid mode: keep hooks installed purely for metrics, even when the authorization path is MCP. Requires
PostToolUsehook to write into the same session state MCP uses. Works today but defeats the "MCP-only" deployment story.Client-supplied usage via
_usagetool arg: extend each MCP tool handler (handleBash, etc.) to accept_tokens_in,_tokens_out,_cost_usdand require the client to supply them. Claude Code does not currently populate these — would need upstream work.Anthropic SDK proxy: the aflock MCP server could proxy Anthropic API calls (or wrap the SDK) so it sees usage responses. Large scope change; probably overkill.
Documented gap: update paper and docs to say
fail-fastlimits only work in hooks mode; MCP supportspost-hoconly.Recommendation: (1) as a short-term workaround + (4) in docs, with (2) as a proper fix tracked for later.
Repro
Related
enforcement: fail-fastadded as an explicit policy schema term)