Commit Graph

22 Commits

Author SHA1 Message Date
Teknium 4270e9ad10 fix: distinguish callable Anthropic bearer sources from OAuth text 2026-09-07 06:07:16 -07:00
Teknium 8c2f3082d8 fix: read only installed SDK credential providers at handoff
Independent review found auto-created mock attributes could replace static credentials. Read the SDK instance's stored provider without triggering attribute synthesis.

Co-authored-by: CloudWishOS <99405975+snipecoder@users.noreply.github.com>

Co-authored-by: BGwill-OUTLOOK <bgwillwork@outlook.com>
2026-09-07 06:07:16 -07:00
Teknium b4464f6fac fix: retain rotating credentials during fallback handoff
Adapt the callable-source diagnosis from snipecoder (#102244) and fallback slice from BGwill-OUTLOOK (#102721), without unrelated reasoning or override changes.

Co-authored-by: CloudWishOS <99405975+snipecoder@users.noreply.github.com>\nCo-authored-by: BGwill-OUTLOOK <bgwillwork@outlook.com>
2026-09-07 06:07:16 -07:00
Teknium d0081d9597 refactor: colocate fallback handoff with client lifecycle 2026-09-07 06:07:16 -07:00
Teknium dca7a90cf8 fix(agent): reclaim background processes by execution owner
Track raw task identities across an agent's turns and match them against
process owner_task_id during close. Session IDs and shared terminal keys
are not process ownership, so the old bulk cleanup missed delegated work.
Preserve parent/sibling processes and consume teardown notifications.

Move task-resource cleanup into the lifecycle mixin, add real-process
isolation regressions, and document background process lifetime.
2026-09-07 04:38:59 -07:00
Teknium ef897bfd7e fix(nous): pre-expiry adoption only ever swaps to a key for the SAME account
Independent review of the first fix found a credential-identity takeover:
_adopt_nous_key_before_expiry() called the singleton resolver
unconditionally, so an agent running on an explicitly supplied (or
pool-selected) account-A key near expiry was moved onto the logged-in
account-B key from auth.json before the A key had even failed, and the next
real SDK request went out as B. That silently changes who is billed.

The adoption now reads the `sub` claim of the key in hand and passes it as
require_account; _try_refresh_nous_client_credentials refuses any
replacement whose `sub` differs (logged at INFO, current key kept). A key
with no `sub` is not adopted proactively at all. The reactive 401 path is
unchanged, and same-account adoption (the keepalive's or a peer's fresh key)
still works.

Verified with the reviewer's own live probe (real AIAgent -> real
prepare_iteration -> real auth.json transaction -> real OpenAI SDK ->
loopback capture): explicit-account case account-A -> account-A,
adopted_fresh=False (was A -> B); same-account still adopts; 12 concurrent
near-expiry agents still 0 x 401.

Tests (2 new): a fresh key for a different account is never adopted; a key
without an account claim is left alone without touching the store.
2026-09-05 06:09:24 -07:00
Teknium 058ad0329e fix(nous): adopt a fresh agent key before the one in hand expires, and start the keepalive in every process that routes to Nous
The Nous agent key lives 3,599 s. In the 1,393-agent refactor run every
in-process agent learned about the hourly expiry from its own 401: 620
authentication_error 401s in the logged window (177 in one hour), each a
failed attempt the model never saw, and the credential pool benched the
sole credential for all of them at once. At 08:30 the storm took the
parent process down.

Two gaps. The proactive refresher (hermes_cli/nous_auth_keepalive.py) is
started only by the gateway and the web server; the CLI process, and every
subagent built inside it, never started it. And even with a fresh key in
the store, nothing adopted it before a request: a request went out with
whatever key the agent was constructed with until it 401'd.

Now _finalize_routing starts the keepalive (idempotent, process-wide,
daemon) whenever an agent resolves onto provider "nous", and
prepare_iteration calls _adopt_nous_key_before_expiry(): the agent key is
a JWT, its exp is read locally, and inside a 180 s skew the store is
re-read under the auth-store lock with force_refresh=False, so the
keepalive's (or a peer's) fresh key is adopted without a POST; when none
exists, ONE refresh runs there instead of N reactive ones after N 401s.
_try_refresh_nous_client_credentials no longer rebuilds the client when
the store returns the key already in hand.

Live A/B (local server: 401s any bearer but FRESH; store patched to hold
FRESH; 40 agents holding a JWT that expires in 30 s fire concurrently):
main 40 x 401 then recover, branch 0 x 401.

Tests (4): far from expiry the store is not touched; inside the skew the
store's fresh key is adopted with force_refresh=False; the same key back
from the store is not re-adopted; a real AIAgent routed to nous starts
the keepalive and one routed to openrouter does not.
2026-09-05 01:27:23 -07:00
Teknium 7a33369e81 simplify(compat): interrupt — drop _ThreadAwareEventProxy/_interrupt_event legacy alias, repoint 2 test files
No runtime consumer read the proxy (terminal_tool/environments call is_interrupted()/set_interrupt()
directly); its only users were tests patching tools.interrupt._interrupt_event, which had no effect on
the code under test. tools/terminal_tool.py's own re-export of the name is owned by another worker.
2026-09-03 14:00:59 -07:00
Teknium c93ace77c2 simplify(compat): config/runtime_provider/plugins/commands/secrets_cli/kanban — drop 96 re-exports (incl. PEP 562 facades) + 3 aliases (get_pre_tool_call_directive/_block_message, get_telegram_handler_factories), repoint 56 callers + 50 test files 2026-09-03 14:00:17 -07:00
Teknium fcbe4acbef simplify(compat): tools/mcp_tool — repoint 20 non-test callers to the defining mcp_tool_* siblings 2026-09-03 13:29:35 -07:00
Teknium d179f28307 simplify(compat): anthropic_adapter — drop 30 re-exports + 1 alias, repoint 22 caller files (32 sites), 38 test files (~125 sites) 2026-09-03 13:16:47 -07:00
Teknium 6e2796b82d review-fix(locks): restore BASE fail-loud lock-slot reads and _is_openai_client_closed truth table
Adolanium review §3 (Medium/Low). BASE shape at every steer/redirect/persist/agent-cache
lock site was: lock = getattr(obj, "_x_lock", None); if lock is not None: with lock: <direct
attribute read>; else: <getattr fallback for object.__new__ test stubs>. The refactor rewrote
several of these as 'with getattr(...) or nullcontext(): getattr(slot, None)', which (a) turned a
missing slot under the lock from a loud AttributeError into silent None and (b) in the gateway
peek helper read the cache without any lock when the lock attribute was absent.

Restored BASE semantics at:
- agent/agent_runtime_helpers.py::_requeue_pending_steer
- agent/interrupt_control.py: steer, redirect, clear_interrupt, _has_pending_redirect,
  _drain_pending_redirect, _drain_pending_steer (new _ic_slot helper: direct read under lock)
- agent/session_persistence.py::_persist_lock (explicit None check + BASE rationale)
- gateway/slash_commands.py::_cached_agent_for (BASE callers read ONLY under the lock; no lock -> None)
- gateway/run_agent_cache.py::_evict_cached_agent (BASE: self._agent_cache direct under lock)
- agent/client_lifecycle.py::_is_openai_client_closed: BASE body + docstring verbatim (outer
  is_closed first; inner _client.is_closed only when _client exists; else False)
- agent/stream_delivery.py::_ensure_stream_writer_state: restore BASE rationale that the lock is
  created unconditionally in agent_init (_STREAM_STATE) and the lazy path is stub-only

A/B (/tmp/rf/rev/ab_lock_fallbacks.py, ab_client_closed.py) is byte-identical BASE vs HEAD on
the reviewer's inputs + edge cases. tests/agent/test_lock_fallback_base_semantics.py pins it
(7 of 25 cases fail on the pre-fix tree).
2026-09-03 12:29:52 -07:00
Teknium e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium 0071ba9965 Merge origin/main (561b053f79) into simp/forwardport: forward-port 220 main commits into the simplified tree 2026-09-03 03:31:03 -07:00
Teknium 29ac7201d8 refactor(agent/client_lifecycle): collapse token/credential guards, one-line log calls, compact docstrings and comments 2026-09-02 23:08:25 -07:00
Teknium b4a0c684ed refactor(agent/client_lifecycle): unify anthropic client build/oauth flag, kwargs credential sync, slot abort label; suppress() for swallowed closes 2026-09-02 22:44:26 -07:00
Teknium 7b699005c8 refactor(agent): AST-identical blank-line compaction in bedrock/codex/client_lifecycle 2026-09-02 21:55:10 -07:00
Teknium f0832b1e92 refactor(agent/client_lifecycle): collapse copilot/vertex refresh tails (979->965 LOC) 2026-09-02 18:54:22 -07:00
Teknium 1dc4fbda99 refactor(agent/client_lifecycle): unify per-request slot cache helpers, credential-adopt helper, split env refresh into phases (1305->979 LOC) 2026-09-02 18:24:56 -07:00
Teknium 45e88aff38 refactor(run_agent): re-wrap comment blocks left with dangling fragments 2026-09-02 13:29:40 -07:00
Teknium 1e778ae7c6 refactor(run_agent): re-wrap compacted docstrings, restore lost issue-ref rationale 2026-09-02 13:29:40 -07:00
Teknium fd54e7faec refactor(run_agent): extract ClientLifecycleMixin (agent/client_lifecycle.py) + agent/lazy_forward.py 2026-09-02 13:29:40 -07:00