Add an optional reasoning_effort field to LLMConfig (e.g. low/medium/high/max)
and forward it to litellm.acompletion in both chat() and chat_stream() when
set. Unset by default so non-OpenAI providers are unaffected. Callers can
still override per-call via kwargs.
Align native context management with the Claude Code / Codex model:
entry-capped tool results, history frozen below the threshold, one
high-quality summary at the wall — instead of the old pipeline that
microcompacted old messages from 60% usage and hid everything past 40
messages behind a snip marker with no summary.
- context pipeline: history below the hard threshold is never rewritten
(model quality and prompt-cache prefixes depend on byte-identical old
messages); the 60% tool-aware microcompact and the 40-message history
snip move to an emergency-only fallback used under overflow pressure
when the summarizer is unavailable or circuit-broken.
- durable compaction (was a stub): at usage >= context_guard.hard_threshold
(now 0.90, soft_threshold removed) the old span is folded into a
9-section summary via the new HistoryCompactor.summarize_runtime_history,
keeping the system head, the seed user request verbatim on every round
(injected session-memory/artifact messages shift the stale
base_prefix_len, so the fold start is structure-aware), and a
pairing-safe recent tail. A previous summary stays foldable, so exactly
one summary exists at a time and rounds chain.
- token accounting anchors on the provider-reported prompt size of the
latest request (max with the local estimate).
- reactive_compaction.circuit_breaker_failures (previously unread) now
stops repeated summarizer failures; provider overflow errors retry
through the same pipeline, summary-first.
- tool-result budget clip keeps head and tail instead of tail-chopping.
- chat-side transcripts get the same treatment: new
MemoryManager.maybe_compact_session_history wires the threshold-gated
maybe_compact_session into secretary, office_ui dispatcher, and
context_loader before prompt building, closing the unbounded-growth
path; dead no-op compactor entries (maybe_compact_after_message,
should_compact_prompt) removed.
Verified by 13 new tests (history sanctity below threshold, multi-round
single-summary/seed-verbatim/chain invariants, breaker, emergency
fallback, provider-overflow end-to-end recovery) plus a live-provider
probe: multi-round compaction with the model completing correctly from
summarized context. Full suite: 1859 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collapse the dual permission stack into one policy. The runtime-side
ToolPermissionResolver (own safe lists, own grant memory, bypassed the
ApprovalEngine whenever it said ALLOW) is deleted; runtime_v2 now consults
ApprovalEngine.predict(), a synchronous fast path reading the same config
and the same persisted allowlist as the async authorize pipeline, so a
grant given anywhere is honored everywhere. permissions.py keeps only a
policy-free adapter; the duplicated permissions_v2 config fields and the
runtime grant persistence loop are removed (stale YAML keys are ignored).
New shell_safety module becomes the single source of truth for shell
classification: flag-audited read-only commands (awk/od/jq/sed -n/diff/
git subcommand table/... auto-allow; find -delete, sort -o, curl -o/-d,
rg --pre still prompt even when the bare name is config-listed),
keyword-aware compound splitting (loop/branch headers no longer poison
grants), expansion-safe $() handling, and fail-closed treatment of
anything unparseable or substitution-bearing.
Grant semantics are rebuilt around derived word-boundary prefixes:
"python3 -c" instead of token bags, interpreter -c/-m kept in the prefix,
bash/eval/sudo never grantable as prefixes, read-only segments exempt
from the every-candidate-must-match rule so a granted command chained
with ls/echo verification passes, and approve-once now records the exact
candidates as a session grant so identical re-runs stop re-prompting.
The authorize heuristic also audits the original command text instead of
the quote-dropping preview (echo "<EOF>" no longer reads as redirection).
Validated live on zz_perm_probe1 (native minimal org): awk/od/ls/cat/
sha256sum ran with zero cards, python3 -c parked once and three different
python3 -c commands then passed via the persisted prefix grant, and an
agent-issued rm -f compound correctly re-prompted showing only the
segments needing approval. Full suite failures are byte-identical to the
pre-change HEAD baseline (27 pre-existing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approval friction (harmless commands kept prompting):
- Persist "Allow for this session" grants to approval_allowlist.yaml under a
new sessions scope (capped LRU), hydrated lazily, so they survive `opc ui`
restarts and re-entering the session instead of living only in memory.
- Safe-prefix matching now accepts compound read-only commands: every segment
must match a safe prefix, and fd-duplication / /dev/null redirections
(2>&1, 2>/dev/null) no longer disqualify a command; real write redirections
(>, >>, <) still do. Default safe prefixes gain common read-only commands
(cd, cat, head, grep, git log, ...).
- First-use approval now gates only MEDIUM+ risk; heuristically LOW actions
proceed without a card.
- Shell-substitution detection flags eval/source only at command position of a
segment (no more false positives on `grep source file`); $(...) and
backticks still flag anywhere.
Approval card timeout redesign (deferred decisions):
- The card's structured approval context (action, allowlist patterns, scopes)
now travels through the escalation event into the persisted card metadata.
- Timeout without a default action no longer marks the card timed out, and the
session-detail reconciler no longer stales deferred-capable cards: the card
stays pending and clickable indefinitely, including across restarts.
- Clicking after the inline wait expired applies the allowlist grant
(approve-once grants the exact command at session scope), resolves the card,
and rewrites the reply to target the parked AWAITING_HUMAN checkpoint so the
task resumes through the normal message pipeline and the retried command
auto-approves. With no parked checkpoint the grant still lands and a helper
reply explains the state.
Verified: approval engine suite (40) incl. new deferred-decision and
compound-command tests, ws_handler + runtime suites green, real escalated
commands from project 999 replayed against the user's config now auto-approve
while pip install / $(...) / rm -rf / write redirects still prompt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- get_context_window() now reads litellm.get_model_info().max_input_tokens
instead of get_max_tokens(), which returns the output cap and severely
under-reported the window for every mapped model (e.g. deepseek 8k vs 1M)
- models litellm cannot map fall back to 128000 with a single warning per
model instead of warning on every call and returning None
- raise LLMConfig.max_tokens default 8192 -> 32768 to match the template
- README: configure the API key directly in llm_config.yaml; document
max_tokens / context_window in the example
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>