From 60881d6888b177525f30906d106a0dda49f6f075 Mon Sep 17 00:00:00 2001 From: Reid <61492567+reidliu41@users.noreply.github.com> Date: Wed, 11 Mar 2026 02:07:29 +0800 Subject: [PATCH] feat(agent): add context size logging before LLM prompt (#810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ci): secrets can't be used in step if conditions [skip-regression-check] (#787) GitHub Actions step-level `if:` doesn't have access to `secrets` context. Replace `if: secrets.X != ''` with `continue-on-error: true` and let the Set token step handle the fallback. Co-authored-by: Claude Sonnet 4.6 * fix(ci): clean up staging pipeline — remove hacks, skip redundant checks [skip-regression-check] (#794) - Remove continue-on-error from staging-ci.yml app token steps (secrets are configured) - Skip test.yml and code_style.yml on PRs targeting staging (staging-ci.yml already runs tests before promoting, promotion PR gets full CI on main) - Allow ironclaw-ci[bot] in Claude Code review for bot-created promotion PRs Co-authored-by: Claude Opus 4.6 * fix(ci): run fmt + clippy on staging PRs, skip Windows clippy [skip-regression-check] (#802) - Remove branches:[main] filter from code_style.yml so it runs on all PRs - Gate clippy-windows with `if: github.base_ref == 'main'` (skip on staging PRs) - Update rollup job to allow skipped clippy-windows - Simplify claude-review.yml to only trigger on labeled event (avoids duplicate runs) Co-authored-by: Claude Opus 4.6 * feat: persist user_id in save_job and expose job_id on routine runs (#709) * feat: persist worker events to DB and fix activity tab rendering In-process Worker (used by Scheduler::dispatch_job) now persists events via save_job_event at key execution points: plan creation, LLM responses, tool_use, tool_result, and job completion/failure/stuck. Event data shapes match the container worker format so the gateway activity tab renders them correctly. Frontend: tool_result errors now show a red X icon with danger styling instead of a silent empty output. The result event falls back to the error field when message is absent. Co-Authored-By: Claude Opus 4.6 * feat: wire RoutineEngine into gateway for direct manual trigger firing Replace the message-channel hack in routines_trigger_handler with a direct call to RoutineEngine::fire_manual(), ensuring FullJob routines dispatch correctly when triggered from the web UI. Inject the engine into GatewayState from Agent::run after construction. Also persists user_id in save_job for both PG and libSQL backends, removes the source='sandbox' filter so all jobs are visible, and exposes job_id on RoutineRunInfo for the frontend job link. Co-Authored-By: Claude Opus 4.6 * fix: remove stale gateway_state argument from Agent::new test call sites The gateway_state parameter was removed from Agent::new during rebase (replaced by post-construction set_routine_engine_slot), but three test call sites still passed the extra None argument. Co-Authored-By: Claude Opus 4.6 * fix: address PR review — restore sandbox source filter, remove blank lines - Revert removal of `source = 'sandbox'` filter in all SandboxStore queries (8 sites across PG and libSQL). Sandbox-specific APIs should stay scoped to sandbox jobs; unified job listing for the Jobs tab should use a separate query path. - Remove extra blank lines in agent_loop.rs and worker.rs that caused formatting CI failure. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 * fix: address review — regenerate Cargo.lock, add user_id regression test - Regenerate Cargo.lock from main's lockfile to eliminate dependency version downgrades (anyhow, syn, etc.) that were churn from rebase. - Add regression test verifying user_id round-trips through save_job and get_job in the libSQL backend. Co-Authored-By: Claude Opus 4.6 * style: remove trailing blank line in libsql jobs.rs [skip-regression-check] Co-Authored-By: Claude Opus 4.6 * test: add Postgres-side regression test for user_id persistence in save_job Mirrors the existing libSQL test (test_save_job_persists_user_id) for the Postgres backend. Gated behind #[cfg(feature = "postgres")] + #[ignore] since it requires a running PostgreSQL instance (integration tier). Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 * feat(agent): add context size logging before LLM prompt --------- Co-authored-by: Henry Park Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Illia Polosukhin --- FEATURE_PARITY.md | 2 +- src/agent/dispatcher.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/FEATURE_PARITY.md b/FEATURE_PARITY.md index d6952cbd..61e32b0c 100644 --- a/FEATURE_PARITY.md +++ b/FEATURE_PARITY.md @@ -53,7 +53,7 @@ This document tracks feature parity between IronClaw (Rust implementation) and O | Trusted-proxy auth mode | ✅ | ❌ | Header-based auth for reverse proxies | | APNs push pipeline | ✅ | ❌ | Wake disconnected iOS nodes via push | | Oversized payload guard | ✅ | 🚧 | HTTP webhook has 64KB body limit + Content-Length check; no chat.history cap | -| Pre-prompt context diagnostics | ✅ | ❌ | Context size logging before prompt | +| Pre-prompt context diagnostics | ✅ | 🚧 | Token breakdown logged before LLM call (conversational dispatcher path); other LLM entry points not yet covered | ### Owner: _Unassigned_ diff --git a/src/agent/dispatcher.rs b/src/agent/dispatcher.rs index 99feed9d..b1678d89 100644 --- a/src/agent/dispatcher.rs +++ b/src/agent/dispatcher.rs @@ -10,6 +10,7 @@ use tokio::task::JoinSet; use uuid::Uuid; use crate::agent::Agent; +use crate::agent::context_monitor::{ContextBreakdown, estimate_text_tokens}; use crate::agent::session::{PendingApproval, Session, ThreadState}; use crate::channels::{IncomingMessage, StatusUpdate}; use crate::context::JobContext; @@ -254,6 +255,27 @@ impl Agent { ); } + // Pre-prompt context diagnostics: log token breakdown before LLM call + { + let breakdown = ContextBreakdown::analyze(&context_messages); + let system_prompt_tokens = + estimate_text_tokens(context.system_prompt.as_deref().unwrap_or("")); + let total_tokens = breakdown.total_tokens + system_prompt_tokens; + tracing::debug!( + iteration, + messages = breakdown.message_count, + total_tokens, + system_prompt_tokens, + system_msg_tokens = breakdown.system_tokens, + user_tokens = breakdown.user_tokens, + assistant_tokens = breakdown.assistant_tokens, + tool_tokens = breakdown.tool_tokens, + tools_available = context.available_tools.len(), + force_text, + "Pre-prompt context diagnostics" + ); + } + let _ = self .channels .send_status(