Skip to content

feat(provider): Wave 1.5/2/3/4 obs, identity, quota, CLI probes - #239

Open
agnitum2009 wants to merge 12 commits into
SeemSeam:mainfrom
agnitum2009:feat/provider-obs-identity-awareness
Open

feat(provider): Wave 1.5/2/3/4 obs, identity, quota, CLI probes#239
agnitum2009 wants to merge 12 commits into
SeemSeam:mainfrom
agnitum2009:feat/provider-obs-identity-awareness

Conversation

@agnitum2009

Copy link
Copy Markdown
Contributor

This PR rolls up the Wave 1.5/2/3/4 Python-side fixes for provider observation, identity awareness, quota handling, and CLI diagnostics.

What's included

  • Kimi completion detection: pane idle/stable is the real completion signal; turn_end alone no longer terminates the turn.
  • Provider/model/account quota buckets with degraded state and retry-after tracking.
  • Unified identity prompt helper and provider-specific injection (Claude --append-system-prompt, Codex/Kimi AGENTS.md).
  • New CLI services: identity and probe.
  • MCP delegation schema/tool updates for roster and peer_status.
  • Provider pane health assessment and ping payload extensions.
  • Related test coverage.

Notes

  • This branch is based on the current upstream/main (v8.0.7).
  • It also carries the already-merged Rust shim work for heartbeat/mailbox/message_bureau that exists earlier in this branch history.
  • Dev artifacts (.test-deps, .codegraph, o13-ccb-ops-unit) are not part of this PR.

CCB Codex and others added 12 commits July 1, 2026 02:09
…essage_bureau

Introduces Rust-backed Python extension modules to reduce Python orchestration
memory for stable subsystems:
- ccb-heartbeat, ccb-mailbox, ccb-message-bureau (Rust implementations)
- ccb-py-heartbeat (heartbeat Python binding)
- ccb-py-mailbox (mailbox_kernel + message_bureau Python binding)

Also brings in the supporting crates they depend on:
- ccb-project, ccb-storage, ccb-storage-classification, ccb-jobs
- ccb-types, ccb-runtime-env, ccb-ui-text

Validation:
- cargo test -p ccb-py-heartbeat -p ccb-py-mailbox -p ccb-jobs -p ccb-storage passes
- cargo clippy -p ccb-py-heartbeat -p ccb-py-mailbox -p ccb-jobs -p ccb-storage clean
- Python smoke tests for both extension modules pass
- Route evaluate_heartbeat through ccb_py_heartbeat when the extension is
  installed and CCB_HEARTBEAT_RUST is not explicitly disabled.
- Fall back to the Python implementation automatically in source checkouts
  that do not have the Rust extension built.
- Convert Rust results back to Python dataclasses so existing consumers
  (job_heartbeat_runtime/tick.py) need no changes.
- Add tests for the env gate and backend selection.
…ility

- Add record_codec compatibility layer so Rust-written mailbox, inbox,
  and lease records carry schema_version/record_type headers expected by
  Python readers.
- Wire ccb-mailbox stores through record_codec for cross-language
  round-trips.
- Extend ccb-py-mailbox bindings with apply_incremental_summary_update,
  upsert_mailbox_summary, rewrite_head, and Ellipsis-aware optional
  active_inbound_event_id mapping.
- Add env-gated mailbox_kernel/__init__.py shim that delegates to the
  Rust extension when available and falls back to Python otherwise.

Relates to PR SeemSeam#237.
Add an opt-in Rust backend for message_bureau via CCB_MESSAGE_BUREAU_RUST=1.
The proven Python implementation remains the default, so production is unaffected.

Rust side:
- Extend ccb-py-mailbox PyO3 bindings for MessageBureauFacade and
  MessageBureauControlService (recording, query, ack_reply, callback edges).
- Add record_codec wrappers for Message/Attempt/Reply/CallbackEdge stores so
  Python readers see schema_version/record_type compatible JSONL.
- Serialize DeliveryScope as single/group/broadcast strings for Python parity.
- Add serde aliases for DeliveryScope in ccb-jobs.
- Validate target mailbox exists in queue_summary before building detail.

Python side:
- lib/message_bureau/__init__.py now imports the Rust extension only when
  CCB_MESSAGE_BUREAU_RUST=1; otherwise it re-exports the existing Python
  classes. Results from Rust are converted back to Python dataclasses.

Validation:
- cargo test -p ccb-mailbox -p ccb-py-mailbox: pass
- cargo clippy -p ccb-mailbox -p ccb-py-mailbox -p ccb-jobs: clean
- test_message_bureau_*.py + test_v2_message_bureau_*.py (85 tests): pass
  with both Python and Rust backends.
…p RSS

Reduce the amount of Python code loaded at ccbd startup by only loading
provider backends and request handlers that are actually needed.

Provider backend filtering:
- Add optional ``providers`` filter to provider_core registry/catalog helpers
  and provider_execution registry so only configured providers are imported.
- ``builtin_backends.build_builtin_backends`` now conditionally imports each
  backend module instead of importing all 18 providers up front.
- ``ccbd.app_runtime.bootstrap`` computes configured providers from
  ``config.agents`` and passes the set to ``build_default_provider_catalog``
  and ``build_default_execution_registry``.
- ``ccbd.app_runtime.service_graph`` builds session bindings once for the
  configured providers and passes them to ``RuntimeService`` and
  ``HealthMonitor`` instead of letting each service rebuild the full map.

Local imports in hot paths:
- Move provider_backends imports out of ``cli.services.provider_hooks`` top
  level and into the per-provider branches of ``_materialize_provider_home``,
  ``provider_hook_home_root``, and ``_route_claude_binary_cache_if_possible``.
- Move ``provider_backends.codex...resolve_codex_home_layout`` and
  ``provider_hooks.activity`` imports into the functions that use them in
  ``ccbd.project_view``.

Lazy handlers:
- ``ccbd.app_runtime.handlers`` no longer imports ``ccbd.handlers`` at the
  top level.  ``register_handlers`` now registers lightweight wrappers that
  import and construct the real handler on the first request.  This defers
  loading all handler modules (and their heavy dependencies such as
  reload_apply_graph and provider_backends.pane_log_support) until first use.

Measured improvement (PYTHONPATH=lib, import ccbd.app):
- RSS delta: ~36 MB -> ~30.4 MB
- provider_backends modules loaded: 454 -> 237
- ``ccbd.handlers`` no longer loaded at import time

Validation:
- test_ccbd*.py + test_provider*.py: 639 passed, 2 skipped
- test_message_bureau_*.py + test_v2_message_bureau_*.py: 85 passed
- test_ccbd_service_graph.py, test_ccbd_project_view.py,
  test_v2_provider_core_registry.py, test_v2_provider_catalog.py,
  test_provider_hook_settings.py: all passed
…ng, doctor & reply diagnostics

Wave 1 of provider-error observability. The Codex account usage-limit banner
previously only appeared on the tmux pane; CCB recorded empty
model_empty_output and reported the agent "healthy". Now the pane content is
parsed and surfaced.

- codex_pane.py: USAGE_LIMIT_MARKERS, usage_limit status, PaneStatus.retry_after, parse_retry_after()
- health_assessment: content-aware ProviderPaneAssessment (pane_signal_state/reason/retry_after/pane_tail) + health_from_pane_signal()
- ping_runtime: provider_health in build_agent_payload diagnostics + handler wiring via health_monitor
- ops_views_doctor: render provider_health line
- codex execution + provider-finish-hook: attach pane_tail/error_kind/retry_after to diagnostics on empty/error turns

Additive and backward-compatible; non-codex providers remain content-blind until
a later wave. 21 new tests; full suite green (only pre-existing mobile-gateway
network failures remain).

Co-Authored-By: Claude <noreply@anthropic.com>
- Kimi completion detection: turn_end is not terminal; pane idle/stable marks real completion

- Provider/model/account quota buckets with degraded state and retry-after

- Identity injection helpers and CLI identity/probe services

- MCP delegation schema/tool updates for roster/peer_status

- Provider pane health assessment and ping payload extensions
…eout

is_pane_alive and get_pane_content called tmux_run_fn directly with no
timeout, so a hung tmux server could block the calling thread indefinitely.
Route both through run_tmux_capture(timeout=2.0) — matching pane_exists
and describe_pane in the same module — so a stuck tmux returns
"unavailable" after 2s instead of hanging. Latent bug surfaced while
diagnosing codex no-reply stalls (not the root cause, but correct hygiene).

Co-Authored-By: Claude <noreply@anthropic.com>
When an ask gets no reply, distinct failure modes (unreachable /
reached-but-quota-full / reached-but-errored / answered-but-CCB-lost-it)
all surfaced identically as status=running + reply=empty, forcing senders
to guess. Add a 17-code NoReplyReason taxonomy so each non-success terminal
job carries an attributable reason the sender can read.

Foundation:
- lib/ccbd/no_reply_reason.py: NoReplyReason enum + descriptions + triage.
- JobRecord.no_reply_reason/no_reply_detail, persisted to jobs.jsonl;
  finalization complete_job attaches/requires it on non-success terminals.
- `ccb why <job_id>` CLI surfaces reason + description + triage hint;
  non-success CCB_REPLY body carries a [no_reply_reason=...] prefix.

Provider can't-answer (B):
- pane-signal -> terminal on the normal poll path (codex/kimi/claude) plus
  a health->terminalize bridge, using TWO-TIER markers: high-confidence
  specific banners (e.g. "hit your usage limit") drive terminalization;
  broad keywords ("quota", "usage limit", "rate limit", ...) stay
  diagnostics-only, so a healthy agent whose output merely discusses these
  topics is not false-positively killed (parse_codex_pane_status strict=,
  high_confidence_signal()).
- codex crash detection (provider_crashed) via pane_dead delegation.

Unreachable (A):
- queue fail-fast stranded degraded-bucket jobs after a grace
  (dispatch_handoff_skipped_degraded) + agent_busy_queue_blocked tag.

Answered-but-not-delivered (C):
- reply_delivery_stalled flag (terminal job with reply unconsumed >120s).
- kimi premature-completion: PANE_FALLBACK_STABLE_SECS 10 -> 45 (heuristic;
  noted insufficient alone — kimi's slow deliberation can still exceed it;
  a stronger finish signal is a follow-up).

Tests: provider_pane_strict_terminalization, false-positive regression,
kimi completion, native CLI completion, execution service.

Co-Authored-By: Claude <noreply@anthropic.com>
kimi-code emitted no authoritative task-complete signal: native TurnEnd is
per-message and the pane idle prompt (`│ >`/`K2.7 Code`/`context:`) shows
BOTH at true completion and during inter-step thinking pauses, so the
pane-idle-stable heuristic closed jobs prematurely and captured only a
deliberation preamble. Adopt the existing CCB_DONE:<anchor> sentinel
protocol (droid uses it) so kimi itself declares completion.

- wrap_kimi_prompt injects the CCB_DONE:<anchor> completion instruction.
- _kimi_sentinel_buffer joins native TurnEnd reply + pane observation reply
  + the carved pane reply region; done_seen = is_done_text(buffer, anchor).
  Authoritative fast completion (reason=kimi_sentinel_complete,
  completion_source=sentinel), bypassing the 45s pane-idle fallback, which
  is kept (kimi_pane_idle_complete) along with native_turn_timeout for the
  no-marker case.
- _extract_kimi_reply_region carves the assistant reply region (after the
  last CCB_REQ_ID prompt anchor, up to the idle input box). This excludes
  the prompt's inline CCB_DONE example (avoids false-fire) and the trailing
  input-box line (so is_done_text's tail scan matches kimi's emitted marker,
  not the box). _strip_kimi_reply_bullet drops the `●`/`•` decoration.
- When done_seen, the reply is re-extracted from the sentinel buffer
  (content before the marker) instead of trusting a possibly-stale partial
  observation.reply (kimi emits multiple bullets; an early poll may have
  captured only the first). Prior reply kept only as a fallback.

Verified end-to-end on a real multi-file task: kimi Reads, answers, emits
CCB_DONE; completion_reason=kimi_sentinel_complete; reply = full answer
before the marker (not the first bullet).

Tests: test_kimi_sentinel_completion.py (sentinel hit, no-marker fallback +
timeout bound, wrong-anchor ignored, reply extraction, prompt-echo
exclusion).

Co-Authored-By: Claude <noreply@anthropic.com>
Codex protocol logs remain the success authority, but a missing terminal event must not leave the active job holding the queue indefinitely. Add a bounded degraded timeout, classify the timeout state by anchor/reply evidence, and preserve provider no-reply detail so operators can choose resend, harvest, or repair actions from evidence instead of guessing.

Constraint: Fix must use daemon-side reliability/no-reply mechanisms and avoid treating pane idle as successful completion.

Rejected: Pane-idle success terminalization | Codex panes can be idle during non-terminal states and would create Kimi-style premature completion risk.

Confidence: high

Scope-risk: moderate

Directive: Keep protocol_log as Codex completion authority; only use reliability timeout for degraded/incomplete queue release with explicit no_reply_detail.

Tested: PYTHONPATH=.test-deps:lib python -m pytest test/test_v2_execution_service.py test/test_provider_execution_service_runtime.py test/test_no_reply_reason.py -q

Tested: PYTHONPATH=.test-deps:lib python -m pytest test -q -> 3508 passed, 2 skipped, 8 known unrelated failures

Not-tested: Live post-timeout Codex job aging for 900 seconds in production.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant