Skip to content

feat: track context window usage #260

Closed
Reese-max wants to merge 14 commits intoopenabdev:mainfrom
Reese-max:feat/context-window-tracking
Closed

feat: track context window usage #260
Reese-max wants to merge 14 commits intoopenabdev:mainfrom
Reese-max:feat/context-window-tracking

Conversation

@Reese-max
Copy link
Copy Markdown

@Reese-max Reese-max commented Apr 12, 2026

Summary

Track ACP context window usage from usage_update session notifications and display it in Discord after each prompt response. Also adds project-specific Copilot code review instructions.

Changes

File Change Description
src/acp/protocol.rs MOD Add UsageUpdate { used, size } variant to AcpEvent; parse via ? — returns None if either field is missing
src/acp/connection.rs MOD Add context_used/context_size AtomicU64 fields; capture values from usage_update via shared classify_notification parser
src/discord.rs MOD Append context usage footer after each prompt response; extracted format_context_footer() helper with 4 unit tests
.github/copilot-instructions.md NEW 67 lines — review philosophy, ACP/concurrency/Discord priority areas, CI-aware skip list

How it works

Agent prompt response
       |
       v
  ACP notification: session/update { sessionUpdate: "usage_update", used: 31434, size: 1000000 }
       |
       v
  classify_notification() -> Some(UsageUpdate { used: 31434, size: 1000000 })
       |
       v
  reader loop: ctx_size.store(1000000, Relaxed)   // size first — reduces sentinel race
               ctx_used.store(31434, Relaxed)
       |
       v
  stream_prompt() finishes -> format_context_footer(31434, 1000000)
       |
       v
  Discord message footer:
  -# 📊 Context: 31434/1000000 tokens (3%)

Discord display

After each agent response, a small footer line appears:

-# 📊 Context: 31434/1000000 tokens (3%)

  • Uses Discord's -# small text syntax (renders as subdued footer)
  • Only shown when context_size > 0 (at least one usage_update received)
  • If footer doesn't fit in the last chunk (<= 2000 chars), sent as a separate message
  • No impact on backends that don't emit usage_update (e.g. Copilot)

Design decisions

  • context_size == 0 means unknowncontext_used can legitimately be 0 (empty context), so context_size is the sentinel
  • ? instead of unwrap_or(0) — missing fields -> None -> counters unchanged (no silent clobbering)
  • Reuses classify_notification — parsing logic stays in protocol.rs, reader loop delegates
  • Ordering::Relaxed — sufficient for best-effort metric counters; size stored before used to reduce sentinel race
  • format_context_footer() helper — pure function, separately testable; percentage clamped to 100 via u128 integer math
  • chars().count() for Discord limit — Discord's 2000 limit is character-based, not byte-based
  • Footer as standalone message — when last chunk is too full, footer sent as its own message (never silently dropped)

Tests

4 new unit tests for format_context_footer:

  • context_footer_none_when_size_zero — returns None when size is 0
  • context_footer_normal_percentage — correct format with "Context:" label
  • context_footer_clamps_over_100used > size clamped to 100%
  • context_footer_100_percent — exact 100% boundary

Copilot instructions

The .github/copilot-instructions.md teaches Copilot's automated PR reviewer about OpenAB-specific concerns:

  • ACP protocol correctness (JSON-RPC routing, notification forwarding, timeout values)
  • Concurrency safety (atomic fields, Mutex across .await, session pool lock scope)
  • Discord API constraints (2000 char limit, 3s autocomplete deadline)
  • Skip CI-covered checks (rustfmt, clippy, tests) to reduce noise
  • >80% confidence threshold

@Reese-max Reese-max requested a review from thepagent as a code owner April 12, 2026 19:25
Copilot AI review requested due to automatic review settings April 12, 2026 19:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for tracking ACP context window usage by capturing usage_update session notifications and exposing the latest used/size values for downstream, context-aware features (e.g., /doctor, auto-compact triggers).

Changes:

  • Added UsageUpdate { used, size } to AcpEvent and extended classify_notification to parse usage_update.
  • Added context_used / context_size atomic counters to AcpConnection.
  • Captured usage_update values in the ACP reader loop and stored them into the new atomics.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/acp/protocol.rs Adds a UsageUpdate event and parses used/size from usage_update notifications.
src/acp/connection.rs Stores latest context usage counters on the connection and updates them when session/update emits usage_update.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/acp/connection.rs Outdated
Comment on lines +139 to +144
// Capture usage_update for context window tracking
if msg.method.as_deref() == Some("session/update") {
if let Some(upd) = msg.params.as_ref()
.and_then(|p| p.get("update"))
{
if upd.get("sessionUpdate").and_then(|v| v.as_str()) == Some("usage_update") {
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reader loop duplicates usage_update parsing logic that now also exists in protocol::classify_notification (and currently uses different semantics). Consider reusing the shared classifier or a small helper to avoid divergence as the ACP schema evolves.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment thread src/acp/connection.rs Outdated
@Reese-max
Copy link
Copy Markdown
Author

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Author

@Reese-max Reese-max left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot review the latest changes — the refactor commit replaced inline parsing with a call to classify_notification(). Please re-analyze.

@Reese-max
Copy link
Copy Markdown
Author

@copilot re-review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/acp/protocol.rs Outdated
Comment on lines +110 to +111
let used = update.get("used").and_then(|v| v.as_u64()).unwrap_or(0);
let size = update.get("size").and_then(|v| v.as_u64()).unwrap_or(0);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In usage_update classification, missing/invalid used or size currently falls back to 0, which can silently clobber previously observed values and makes 0 indistinguishable from “field absent/unknown” (notably for backends that emit the event but omit these fields). Consider returning None unless both fields are present, or only updating the fields that are present (e.g., keep prior value when a field is missing).

Suggested change
let used = update.get("used").and_then(|v| v.as_u64()).unwrap_or(0);
let size = update.get("size").and_then(|v| v.as_u64()).unwrap_or(0);
let used = update.get("used").and_then(|v| v.as_u64())?;
let size = update.get("size").and_then(|v| v.as_u64())?;

Copilot uses AI. Check for mistakes.
Comment thread src/acp/connection.rs
Comment on lines +142 to +147
if let Some(crate::acp::protocol::AcpEvent::UsageUpdate { used, size }) =
crate::acp::protocol::classify_notification(&msg)
{
ctx_used.store(used, Ordering::Relaxed);
ctx_size.store(size, Ordering::Relaxed);
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context_used and context_size are updated independently with relaxed atomics. Any reader that loads both to compute a ratio can observe a mixed snapshot (e.g., used from the new update with size from the previous one). If downstream consumers will read them as a pair, consider providing a snapshot getter using a version/sequence approach, or storing both values behind a small lock so reads are consistent.

Copilot uses AI. Check for mistakes.
Comment thread src/acp/connection.rs
Comment on lines +55 to +57
/// Context window usage from the latest `usage_update` ACP notification.
pub context_used: Arc<std::sync::atomic::AtomicU64>,
pub context_size: Arc<std::sync::atomic::AtomicU64>,
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public fields context_used/context_size default to 0, which conflates “unknown/not provided by backend yet” with a legitimate 0 value. Since these are part of the connection’s public API surface, consider documenting the sentinel semantics (e.g., 0 == unknown) or representing “unknown” explicitly (Option/sentinel like u64::MAX) to avoid ambiguous downstream behavior.

Copilot uses AI. Check for mistakes.
@Reese-max
Copy link
Copy Markdown
Author

Re: atomic consistency concern — intentional trade-off. The only current reader is /doctor display, which tolerates a brief mixed snapshot. Adding a lock would be premature. Happy to add a snapshot helper if paired reads become critical.

The other two suggestions (require both fields via ?, document 0-sentinel) are addressed in commit 160b698.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Reese-max
Copy link
Copy Markdown
Author

嘿嘿終於 這是我第一次PR 下次不會再出現這些問題了 抱歉抱歉

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

isxreese and others added 6 commits April 13, 2026 06:41
Capture `used` and `size` from ACP `usage_update` session notifications
into atomic counters on AcpConnection. This enables downstream features
like context display in /doctor and auto-compact triggers.

- Add `context_used` / `context_size` AtomicU64 fields to AcpConnection
- Capture values from `usage_update` in the reader loop
- Add `UsageUpdate { used, size }` variant to AcpEvent enum
- Add `usage_update` branch to classify_notification

Verified with Claude Code (1M ctx), Codex CLI (258K ctx), and Gemini CLI
(schema-defined). Zero-cost when no usage_update is emitted (Copilot).
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Replace inline usage_update parsing in the reader loop with a call
to the shared classify_notification() helper (protocol.rs), keeping
the parsing logic in one place and avoiding divergence as ACP evolves.
Address review feedback:
- protocol.rs: use `?` instead of unwrap_or(0) so a usage_update
  missing either field returns None (preserves previous counters).
- connection.rs: document that 0 means "no usage_update received yet".

The two atomic fields are intentionally independent (no lock) because
the only current reader is /doctor display, which tolerates a brief
mixed snapshot. A snapshot helper can be added when paired reads
become critical.
Add `.github/copilot-instructions.md` to guide GitHub Copilot's
automated PR reviews with project-specific context:

- ACP protocol correctness (JSON-RPC routing, notification handling)
- Concurrency safety (atomic fields, Mutex across await, pool locks)
- Discord API constraints (2000 char limit, 3s autocomplete deadline)
- Skip CI-covered checks (rustfmt, clippy, tests)
- >80% confidence threshold to reduce noise

Modeled after block/goose's production-grade instructions.
3440 chars — within the 4000 char limit.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/acp/connection.rs Outdated
pub last_active: Instant,
pub session_reset: bool,
/// Context window usage from the latest `usage_update` ACP notification.
/// A value of 0 means no `usage_update` has been received yet (unknown).
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment says a value of 0 means no usage_update has been received yet, but used can legitimately be 0 (e.g., empty/new context), so this makes the “unknown” state ambiguous and potentially misleading for downstream consumers. Consider tracking “unknown” separately (e.g., an AtomicBool flag), or document a sentinel based on context_size == 0 / Option-like encoding instead.

Suggested change
/// A value of 0 means no `usage_update` has been received yet (unknown).
/// `context_used` may legitimately be 0 (for example, for a new or empty context).
/// Treat `context_size == 0` as meaning that no `usage_update` has been received yet.

Copilot uses AI. Check for mistakes.
Comment thread .github/copilot-instructions.md Outdated
- `session/request_permission` must always get a response (auto-allow or forwarded)
- `session/update` notifications must not be consumed — forward to subscriber after capture
- `usage_update`, `available_commands_update`, `tool_call`, `agent_message_chunk` must be classified correctly
- Timeout values: initialize=90s, session/new=120s, others=30s (Gemini cold-start is slow)
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This instruction claims timeout values include initialize=90s, but the current implementation in acp/connection.rs uses 30s for all methods except session/new (120s); this mismatch can cause reviewers to flag the wrong behavior or miss real issues. Update the instruction to match the current code, or explicitly label these values as a desired target rather than current behavior.

Suggested change
- Timeout values: initialize=90s, session/new=120s, others=30s (Gemini cold-start is slow)
- Timeout values currently implemented: `session/new`=120s; all other methods, including `initialize`, use 30s

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/acp/connection.rs
Comment on lines +55 to +59
/// Context window usage from the latest `usage_update` ACP notification.
/// `context_used` may legitimately be 0 (e.g., new or empty context).
/// Treat `context_size == 0` as meaning no `usage_update` has been received yet.
pub context_used: Arc<std::sync::atomic::AtomicU64>,
pub context_size: Arc<std::sync::atomic::AtomicU64>,
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context_used and context_size are updated via separate atomic stores, so readers can observe a mixed snapshot (new used with old size, or vice versa). Either document that these values are best-effort and may be temporarily inconsistent, or provide a helper that reads them consistently (e.g., read size, then used, then re-read size and retry if it changed).

Copilot uses AI. Check for mistakes.
@Joseph19820124
Copy link
Copy Markdown

✦ 是的,在没有这个 PR 之前,现在的 Discord 完全没有这个功能。

从刚才的搜索结果来看:

  1. 协议解析层 (src/acp/protocol.rs): 现在的代码没有处理 usage_update 类型的通知,遇到这种消息会被忽略掉。
  2. 数据模型层 (src/acp/connection.rs): 现在的 AcpConnection 结构体里没有存储 context_used 或 context_size 的变量。
  3. UI 展示层 (src/discord.rs 或 reactions.rs): 因为底层没有数据,Discord 侧也无法展示任何关于 Context 消耗的信息。

PR #260 补齐了这块拼图:它在底层让 OpenAB 能够“听懂” Agent 发出的 Context 使用量通知,并实时存下来。这样之后就可以在 Discord 消息里通过 Emoji 进度条、或者特定命令(如
/status)来告诉用户“上下文已用 80%,准备清理”。

没有这个 PR 的话,用户在 Discord 上对 Agent 的上下文消耗情况是完全未知的。

@Reese-max
Copy link
Copy Markdown
Author

Re: atomic consistency — this is an intentional trade-off documented in the field's doc comment. The two counters are updated from the same usage_update payload in consecutive stores (no interleaving I/O), so the inconsistency window is nanoseconds. The only current reader is diagnostic display (/doctor), which tolerates a brief mixed snapshot. A snapshot helper (read-size, read-used, re-read-size, retry) adds complexity without current benefit — happy to add one when paired reads become critical.

@Joseph19820124
Copy link
Copy Markdown

通过分析 PR #260 的变动文件:

  1. .github/copilot-instructions.md: Copilot 的评审指令。
  2. src/acp/connection.rs: 增加了 context_used 和 context_size 字段。
  3. src/acp/protocol.rs: 增加了对 usage_update 协议的解析。

这个 PR 是如何“准备”让 Discord 展示这些值的?
这个 PR 的设计是“底层先行”。它在 AcpConnection 结构体中通过 Arc 暴露了这两个值:

1 // src/acp/connection.rs
2 pub struct AcpConnection {
3 // ...
4 pub context_used: Arcstd::sync::atomic::AtomicU64,
5 pub context_size: Arcstd::sync::atomic::AtomicU64,
6 }

这意味着,虽然这个 PR 还没在 src/discord.rs 里加代码,但它已经打通了数据链路。后续在处理 Discord 消息(例如用户输入一个 /usage 指令,或者在每一轮对话结束时)时,Discord 的 Handler
只需要简单地调用:

1 let used = connection.context_used.load(Ordering::Relaxed);
2 let size = connection.context_size.load(Ordering::Relaxed);
3 // 然后计算百分比并回复给 Discord 用户

为什么作者不直接把 Discord 展示也做了?
从 PR 的 Summary 和 Design decisions 来看,作者的意图是:

  1. 解耦:先确保 ACP 协议能正确解析出这两个值,并线程安全地存储在 AcpConnection 中。
  2. 指令驱动:PR 中很大一部分工作是 .github/copilot-instructions.md。这个文件里特别提到了 “Discord API 限制(2000字符限制)” 和 “异步下的并发安全”。

结论:这个 PR 目前只是“准备好了弹药”。它让 OpenAB 的核心引擎具备了感知 Context 使用量的能力,但真正的 Discord 展示逻辑(比如在回复消息末尾加一个进度条 [▓▓░░░░] 25%)可能计划在下一个
PR 或者是作为这个 PR 的后续提交(如果还没 merge 的话)。

Append a small footer to each agent response showing the current
context window utilization, e.g.:

  📊 Context: 31434/1000000 tokens (3%)

Only shown when context_size > 0 (i.e., at least one usage_update
has been received). Uses Discord's -# small text syntax. No impact
on backends that don't emit usage_update.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/discord.rs Outdated
let ctx_size = conn.context_size.load(std::sync::atomic::Ordering::Relaxed);
let final_content = if ctx_size > 0 {
let ctx_used = conn.context_used.load(std::sync::atomic::Ordering::Relaxed);
let pct = (ctx_used as f64 / ctx_size as f64 * 100.0).round() as u64;
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The % computation can produce nonsensical values if ctx_used > ctx_size (backend bug or a mixed atomic snapshot) and the float→int cast will saturate for huge ratios; clamp the percentage to 100 and prefer integer math (e.g., via u128) to avoid float precision/saturation surprises.

Suggested change
let pct = (ctx_used as f64 / ctx_size as f64 * 100.0).round() as u64;
let pct = (((ctx_used as u128) * 100 + (ctx_size as u128) / 2) / (ctx_size as u128))
.min(100) as u64;

Copilot uses AI. Check for mistakes.
Comment thread src/discord.rs Outdated
Comment on lines +592 to +597
// Append context window usage footer if available.
let ctx_size = conn.context_size.load(std::sync::atomic::Ordering::Relaxed);
let final_content = if ctx_size > 0 {
let ctx_used = conn.context_used.load(std::sync::atomic::Ordering::Relaxed);
let pct = (ctx_used as f64 / ctx_size as f64 * 100.0).round() as u64;
format!("{final_content}\n-# 📊 Context: {ctx_used}/{ctx_size} tokens ({pct}%)")
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds new user-visible output formatting for the final Discord message (context footer) but there are no tests covering the formatting/percent logic; consider extracting the footer formatting into a small pure helper and adding unit tests for the ctx_size == 0 and ctx_used > ctx_size cases.

Suggested change
// Append context window usage footer if available.
let ctx_size = conn.context_size.load(std::sync::atomic::Ordering::Relaxed);
let final_content = if ctx_size > 0 {
let ctx_used = conn.context_used.load(std::sync::atomic::Ordering::Relaxed);
let pct = (ctx_used as f64 / ctx_size as f64 * 100.0).round() as u64;
format!("{final_content}\n-# 📊 Context: {ctx_used}/{ctx_size} tokens ({pct}%)")
fn format_context_footer(ctx_used: u64, ctx_size: u64) -> Option<String> {
if ctx_size == 0 {
return None;
}
let pct = (ctx_used as f64 / ctx_size as f64 * 100.0).round() as u64;
Some(format!(
"-# 📊 Context: {ctx_used}/{ctx_size} tokens ({pct}%)"
))
}
// Append context window usage footer if available.
let ctx_size = conn.context_size.load(std::sync::atomic::Ordering::Relaxed);
let ctx_used = conn.context_used.load(std::sync::atomic::Ordering::Relaxed);
let final_content = if let Some(footer) = format_context_footer(ctx_used, ctx_size) {
format!("{final_content}\n{footer}")

Copilot uses AI. Check for mistakes.
- Skip footer when message is >1900 chars to prevent it splitting
  into a separate Discord message
- Shorten "📊 Context: X/Y tokens (N%)" to "📊 X/Y tokens (N%)"
Instead of hiding the context footer on long messages, append it to
the last chunk if it fits within the 2000-char limit. This ensures
users always see context usage — especially important for long
responses that consume more context window.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/discord.rs Outdated
Comment on lines +605 to +612
// Append context footer to the last chunk if it fits.
let content = if i == last_idx && !ctx_footer.is_empty()
&& chunk.len() + ctx_footer.len() < 2000
{
format!("{chunk}{ctx_footer}")
} else {
chunk.to_string()
};
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The footer-fit check uses chunk.len()/ctx_footer.len() (byte length), but Discord’s 2000 limit (and format::split_message) are based on Unicode character count; use .chars().count() (or a shared helper) so the footer isn’t incorrectly dropped for non-ASCII content.

Copilot uses AI. Check for mistakes.
Comment thread src/discord.rs
Comment on lines 602 to +610
let chunks = format::split_message(&final_content, 2000);
let last_idx = chunks.len().saturating_sub(1);
for (i, chunk) in chunks.iter().enumerate() {
// Append context footer to the last chunk if it fits.
let content = if i == last_idx && !ctx_footer.is_empty()
&& chunk.len() + ctx_footer.len() < 2000
{
format!("{chunk}{ctx_footer}")
} else {
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the context footer is only appended if it fits in the last chunk; when the final chunk is near the 2000-char limit, the footer is silently omitted, which contradicts “display it in Discord after each prompt response”. Consider emitting the footer as its own extra message/chunk when it doesn’t fit.

Copilot uses AI. Check for mistakes.
- Extract format_context_footer() helper with unit tests
- Clamp percentage to 100 via u128 integer math (handles used > size)
- Use chars().count() instead of byte .len() for Discord 2000-char limit
- Send footer as separate message when it doesn't fit in last chunk
- Document atomic consistency semantics on context_used/context_size

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/discord.rs
Comment on lines +600 to +607
// Append context footer to the last chunk if it fits (char count, not bytes).
let content = if i == last_idx {
if let Some(ref footer) = ctx_footer {
if chunk.chars().count() + footer.chars().count() < 2000 {
format!("{chunk}{footer}")
} else {
chunk.to_string()
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The footer append check uses < 2000, so a chunk that would be exactly 2000 characters after appending the footer won’t get it; the follow-up >= 2000 check will then send the footer as a separate message even though it would have fit exactly. Consider using <= 2000 for the append condition and > 2000 (or equivalent) for the separate-message condition to avoid this off-by-one behavior.

Copilot uses AI. Check for mistakes.
Comment thread src/discord.rs
if let Some(ref footer) = ctx_footer {
let last_chunk = chunks.last().map(|c| c.as_str()).unwrap_or("");
if last_chunk.chars().count() + footer.chars().count() >= 2000 {
let _ = channel.say(&ctx.http, footer).await;
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the footer doesn’t fit and is sent as a separate message, it currently includes a leading newline from format_context_footer, which will render as an empty first line in Discord. Consider sending a newline-less variant when posting the footer as a standalone message (or trimming leading \n in this branch).

Suggested change
let _ = channel.say(&ctx.http, footer).await;
let standalone_footer = footer.trim_start_matches('\n');
let _ = channel.say(&ctx.http, standalone_footer).await;

Copilot uses AI. Check for mistakes.
Comment thread src/acp/connection.rs Outdated
Comment on lines +60 to +62
/// atomic stores, so readers may observe a mixed snapshot (e.g., new `used`
/// with old `size`). This is acceptable for best-effort display purposes;
/// any consumer requiring a consistent pair should use the snapshot helper.
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The struct doc comment mentions “any consumer requiring a consistent pair should use the snapshot helper,” but there doesn’t appear to be a snapshot helper in this module. Either add the referenced helper (e.g., a method that reads both atomics consistently) or adjust the comment so it doesn’t point to a non-existent API.

Suggested change
/// atomic stores, so readers may observe a mixed snapshot (e.g., new `used`
/// with old `size`). This is acceptable for best-effort display purposes;
/// any consumer requiring a consistent pair should use the snapshot helper.
/// atomic stores, so readers may observe a mixed pair (e.g., new `used`
/// with old `size`). This is acceptable for best-effort display purposes.
/// Code that requires a coordinated view of both values must use additional
/// synchronization instead of reading these atomics independently.

Copilot uses AI. Check for mistakes.
isxreese and others added 2 commits April 13, 2026 11:35
- Use <= 2000 for footer append check (fixes off-by-one at boundary)
- Use > 2000 for separate-message condition (consistent with above)
- trim_start_matches('\n') on standalone footer (no empty first line)
- Remove reference to non-existent snapshot helper in doc comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Footer string no longer carries a leading '\n' — callers add the
separator explicitly. This eliminates the trim_start_matches hack
when sending the footer as a standalone message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/acp/connection.rs Outdated
Comment thread src/discord.rs
- Store ctx_size before ctx_used to reduce race where reader sees
  size=0 (sentinel for "no data") while used is already updated
- Add "Context:" label to footer to match PR description format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Reese-max Reese-max changed the title feat: track context window usage and add Copilot review instructions feat: track context window usage Apr 13, 2026
@Reese-max Reese-max closed this Apr 14, 2026
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.

4 participants