Class: Insika::EdgeLimiter
- Inherits:
-
Middleware
- Object
- Middleware
- Insika::EdgeLimiter
- Defined in:
- lib/insika/edge_limiter.rb
Overview
The production edge (item 33 / §12 G7): THE named place where volume/cost abuse is cut. A Middleware with two independent limits, both OPT-IN (nil/0 = off — a bare wiring behaves exactly as before):
· chat rate limit — turn ATTEMPTS per chat per window. Counted on entry
(a blocked attempt still counts), so a flood keeps hitting the wall.
· agent token ceiling — total tokens per agent per window. Checked on entry
against the accumulated ledger; the turn's own usage is recorded AFTER the
terminal returns (the Middleware wraps stages 5-9, so state.usage is set).
Config resolution, per turn (configuration over convention):
profile.limits[:chat_rate_limit / :agent_token_ceiling] — per-agent override
settings["edge"] — platform default
A per-agent 0 explicitly disables a platform default for that agent.
On breach it uses the graceful-halt contract (RFC-0009 §3.1): halt_response
(the safe reply) + guardrail_block (audit -> :guardrail_blocked) and does NOT
call nxt — the turn completes with ZERO LLM calls. It sits BEFORE the
InputGuardrail in the stack so a flood can't spend the LLM moderator either.
Constant Summary collapse
- CHAT_KIND =
"chat"- TOKENS_KIND =
"tokens"- DEFAULT_CHAT_WINDOW =
seconds
60- DEFAULT_TOKEN_WINDOW =
seconds (daily ceiling)
86_400- DEFAULT_RESPONSE =
Neutral fallback, same contract as Safety::SafeResponses (pt-BR — the pilot's language; override via settings edge.limit_response).
"Estou recebendo muitas mensagens agora. Aguarde um " \ "momento e tente novamente, por favor."
Instance Method Summary collapse
- #call(state, &nxt) ⇒ Object
-
#initialize(ledger:, settings_store: nil) ⇒ EdgeLimiter
constructor
A new instance of EdgeLimiter.
Constructor Details
#initialize(ledger:, settings_store: nil) ⇒ EdgeLimiter
Returns a new instance of EdgeLimiter.
38 39 40 41 |
# File 'lib/insika/edge_limiter.rb', line 38 def initialize(ledger:, settings_store: nil) @ledger = ledger @settings = settings_store end |
Instance Method Details
#call(state, &nxt) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/insika/edge_limiter.rb', line 43 def call(state, &nxt) edge = platform_edge limits = state.profile.limits || {} # A resume (crash/pause recovery) re-enters the pipeline for a turn that was # ALREADY admitted: re-counting it would swallow a legitimate message with # the rate-limit reply exactly when the window is saturated. Entry checks # are skipped; the turn's usage still lands on the ledger below. resumed = state.resumed if !resumed && (limit = positive(limits.key?(:chat_rate_limit) ? limits[:chat_rate_limit] : edge["chat_rate_limit"])) breach = check_chat_rate(state, limit, edge) return block(state, edge, **breach) if breach end # NB: a per-agent key PRESENT with nil (e.g. an imported pack carrying # `"chat_rate_limit": null`) reads as OFF for that agent, not "inherit". if (ceiling = positive(limits.key?(:agent_token_ceiling) ? limits[:agent_token_ceiling] : edge["agent_token_ceiling"])) token_window = positive(edge["agent_token_window"]) || DEFAULT_TOKEN_WINDOW unless resumed spent = @ledger.count(TOKENS_KIND, state.profile.id.to_s, window: token_window) if spent >= ceiling return block(state, edge, category: :token_ceiling, detail: "agent #{state.profile.id}: #{spent}/#{ceiling} tokens per #{token_window}s") end end record_after = token_window end result = nxt.call(state) record_usage(state, record_after) if record_after result end |