ledger.load now catches JSONDecodeError/ValueError and falls back to
an empty ledger, matching the defensive pattern already used by
sigil.load. Prevents a truncated or manually corrupted ledger file
from permanently breaking build_context for that stream.
https://claude.ai/code/session_01K7BWJY2gUoJi6dq91Yc7nx
_run_compression persists extracted substrings into ledger and sigil
files on disk. Previously it ran on raw messages, so credentials
matching decision/blocker patterns would be written to ~/.ra2/ in
plaintext. Now redact.redact_messages() is applied first, ensuring
only sanitised text reaches any disk-persisting path.
The redundant redact.redact(prompt) on the final assembled prompt is
removed since all inputs are already clean. The shrink loop correctly
re-estimates tokens after each reassembly.
https://claude.ai/code/session_01K7BWJY2gUoJi6dq91Yc7nx
Use ~3.3 chars/token base ratio (more conservative than the previous ~4)
and apply a non-ASCII density penalty that shifts toward ~2.5 chars/token
for CJK, emoji, or symbol-heavy text. Prevents underestimation that
could bypass the token gate on code-heavy or multilingual prompts.
https://claude.ai/code/session_01K7BWJY2gUoJi6dq91Yc7nx
Replace plain-text σN format with structured JSON per stream:
- EVENT layer: decision causality log with operator→constraint→decision
triples and ISO8601 timestamps. Max 15 entries, FIFO trim, dedup.
- STATE layer: authoritative snapshot with arch, risk, mode sections.
Overwritten (not appended) on each update cycle.
- Fields capped at 64 chars, file size capped at 8KB (configurable).
- Atomic writes via tmp+rename.
- Corrupt/partial JSON gracefully falls back to empty template.
Sigil is internal-only by default:
- Not included in model prompts unless DEBUG_SIGIL=true
- When debug enabled, injected as === INTERNAL SIGIL SNAPSHOT ===
- Never exposed to Discord users unless debug flag active
context_engine.py updated:
- Compression pass emits (operator, constraint, decision) triples
- Sigil section gated behind DEBUG_SIGIL flag
85 tests passing (up from 64).
https://claude.ai/code/session_01K7BWJY2gUoJi6dq91Yc7nx
Add deterministic context control layer that intercepts prompt
construction without modifying existing architecture:
- context_engine.py: single choke point (build_context) that assembles
structured prompts from ledger + sigil + live window, with token
budget enforcement and automatic window shrinking
- ledger.py: bounded per-stream JSON state (orientation, blockers,
open questions, delta) with hard field/list limits
- sigil.py: FIFO shorthand memory (max 15 entries) with deterministic
rule-based generation from message patterns
- token_gate.py: fast token estimation (~4 chars/token) and hard cap
enforcement with configurable MAX_TOKENS/LIVE_WINDOW
- redact.py: secret pattern detection (Discord, OpenAI, Anthropic,
AWS, Slack, GitHub, Telegram, Bearer, generic key=value) replaced
with [REDACTED_SECRET] before any output path
All 64 tests passing. No modifications to existing agent spawning,
model routing, tool system, or Discord relay architecture.
https://claude.ai/code/session_01K7BWJY2gUoJi6dq91Yc7nx
* fix: treat HTTP 503 as failover-eligible for LLM provider errors
When LLM SDKs wrap 503 responses, the leading "503" prefix is lost
(e.g. Google Gemini returns "high demand" / "UNAVAILABLE" without a
numeric prefix). The existing isTransientHttpError only matches
messages starting with "503 ...", so these wrapped errors silently
skip failover — no profile rotation, no model fallback.
This patch closes that gap:
- resolveFailoverReasonFromError: map HTTP status 503 → rate_limit
(covers structured error objects with a status field)
- ERROR_PATTERNS.overloaded: add /\b503\b/, "service unavailable",
"high demand" (covers message-only classification when the leading
status prefix is absent)
Existing isTransientHttpError behavior is unchanged; these additions
are complementary and only fire for errors that previously fell
through unclassified.
* fix: address review feedback — drop /\b503\b/ pattern, add test coverage
- Remove `/\b503\b/` from ERROR_PATTERNS.overloaded to resolve the
semantic inconsistency noted by reviewers: `isTransientHttpError`
already handles messages prefixed with "503" (→ "timeout"), so a
redundant overloaded pattern would classify the same class of errors
differently depending on message formatting.
- Keep "service unavailable" and "high demand" patterns — these are the
real gap-fillers for SDK-rewritten messages that lack a numeric prefix.
- Add test case for JSON-wrapped 503 error body containing "overloaded"
to strengthen coverage.
* fix: unify 503 classification — status 503 → timeout (consistent with isTransientHttpError)
resolveFailoverReasonFromError previously mapped status 503 → "rate_limit",
while the string-based isTransientHttpError mapped "503 ..." → "timeout".
Align both paths: structured {status: 503} now also returns "timeout",
matching the existing transient-error convention. Both reasons are
failover-eligible, so runtime behavior is unchanged.
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(docker): pin base images to SHA256 digests for supply chain security
Pin all 9 Dockerfiles to immutable SHA256 digests to prevent supply chain
attacks where a compromised upstream image could be silently pulled into
production builds.
Also add Docker ecosystem to Dependabot configuration for automated
digest updates.
Images pinned:
- node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
- node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
- debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
- ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b
Fixes#7731
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(docker): add digest pinning regression coverage
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>