Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
790d3ea
♻️ refactor: consolidate context building into ContextManager
May 20, 2026
94a287f
fix(db): add missing semicolon to is_new index in init.sql
wuyuanfr May 27, 2026
a628e76
♻️ refactor: route rendered system_prompt through build_context_compo…
wuyuanfr May 25, 2026
16a176d
🧪 test: add prompt-equivalence golden test
wuyuanfr May 25, 2026
566d8c6
🔧 chore: log when a ContextStrategy drops a component
wuyuanfr May 25, 2026
a5b522a
✨ feat(sdk): pass-through extra_body to chat.completions.create
wuyuanfr May 25, 2026
debefb3
✨ feat(sdk): pre-truncate long observations under ContextManager
wuyuanfr May 25, 2026
37b24e9
✨ feat(sdk): add benchmark instrumentation APIs on ContextManager
wuyuanfr May 25, 2026
0305481
📦 chore: import sdk/benchmark and sdk/ctx_debugger from feature branch
wuyuanfr May 25, 2026
d6d2709
🧪 test: add minimal end-to-end smoke for benchmark-on-refactor
wuyuanfr May 25, 2026
8f6b3b1
🐛 fix(benchmark): r.get is a method not a subscriptable
wuyuanfr May 25, 2026
5c38be4
🐛 fix(test): allow prompt_type kwarg in compress side_effect mocks
wuyuanfr May 26, 2026
6539899
🧪 fix(test): restore real sys.modules after stub-injecting tests
wuyuanfr May 26, 2026
cf2afc1
♻️ refactor(sdk): implement piecewise component assembly for all 4 va…
wuyuanfr May 26, 2026
aab560f
✨ feat(sdk): add compress_history_offline standalone function
wuyuanfr May 26, 2026
63705b2
✨ feat(sdk): pass-through max_tokens to chat.completions.create
wuyuanfr May 26, 2026
42b3675
📝 docs: translate Chinese markdown files to English in benchmark and …
wuyuanfr May 26, 2026
a49dd10
🔄 sync(benchmark): pick eventqa_eval + agent_runner from feature
wuyuanfr May 26, 2026
9e65f2c
sync(benchmark): pick run_longmemeval + run_with_debugger+ summary_sc…
wuyuanfr May 27, 2026
f393ca5
🔄 sync(benchmark): pick manual_cases harness from feature
wuyuanfr May 27, 2026
630aa89
🔧 chore(benchmark): align build_agent_run_info max_tokens default to …
wuyuanfr May 27, 2026
f199c7a
update system and summary prompt
liudfgoo May 27, 2026
776f4fd
readme and eval res
liudfgoo May 27, 2026
1c041a2
add fallback and build_compressed_snapshot
liudfgoo May 27, 2026
731503b
add acon output res
liudfgoo May 27, 2026
46dd020
add default summary prompt argument
May 27, 2026
47f6b91
add cases
May 27, 2026
7cb5fa5
add reports results
May 27, 2026
7b8cf05
add static summary inspections
May 27, 2026
dff5114
Merge branch 'develop' into feat/benchmark-on-refactor
JasonW404 May 27, 2026
ed5c9fa
fix(sdk): restore indentation on extra_body after merge
wuyuanfr May 27, 2026
0c36c1d
fix(sdk,test): resolve merge fallout in AgentConfig and align tests
wuyuanfr May 27, 2026
0f3fa4c
chore(sdk/benchmark): stop tracking eval datasets and outputs
wuyuanfr May 28, 2026
e254c7b
chore(sdk/benchmark): stop tracking manual_cases reports and inspections
wuyuanfr May 28, 2026
5f8c993
Temporarily comment out code for SDK dev to avoid Docker dependency; …
liudfgoo May 28, 2026
eaf3dea
add extract_invoked_tools
liudfgoo May 28, 2026
7202580
code analysis for extracting tool
liudfgoo May 28, 2026
2b1aeb3
split agent_context.py and add offload or reload func
liudfgoo May 28, 2026
b0f98ba
temp scripts to check agent_context
liudfgoo May 29, 2026
d870fa8
add reload tool
liudfgoo May 30, 2026
f6730ff
add ReloadOriginalContextTool
liudfgoo May 30, 2026
8f7498d
update test_import to check import_path quickly
liudfgoo May 30, 2026
83b0596
add reload tool info and enabled_reload para
liudfgoo May 30, 2026
96f3189
delete history.md and test current compression
liudfgoo May 31, 2026
f4638d7
logger --> print for token stat
liudfgoo May 31, 2026
2c0d4af
test current
liudfgoo May 31, 2026
de7bbae
temp doc for understand
liudfgoo May 31, 2026
a708ea3
Actionstep is self-contained, remove tool-call and observation pair c…
liudfgoo Jun 1, 2026
e292906
add tool-call arguments signature
liudfgoo Jun 1, 2026
a705790
no change
liudfgoo Jun 1, 2026
087672c
add offload
liudfgoo Jun 1, 2026
9a11ea4
remove extract tool func
liudfgoo Jun 1, 2026
018db44
add extract_invoked_tool_signatures
liudfgoo Jun 1, 2026
6afbe7d
update summary_config
liudfgoo Jun 1, 2026
a59f0c9
update test func
liudfgoo Jun 1, 2026
c4511e9
update effective summary prompt
liudfgoo Jun 1, 2026
5a49c33
offload and _render_segment
liudfgoo Jun 1, 2026
5e869bd
offload prompt
liudfgoo Jun 1, 2026
620109f
add OffloadStore para
liudfgoo Jun 3, 2026
1f8821f
add extra func for offload_store: Evict oldest entry
liudfgoo Jun 3, 2026
8b97673
update step render, especially for offload
liudfgoo Jun 3, 2026
7683caf
needs_raw for offload and Consider the token length of the marker its…
liudfgoo Jun 3, 2026
16433a9
max_offload_entries or chars
liudfgoo Jun 3, 2026
05815e6
adjust offload
liudfgoo Jun 3, 2026
9c04069
test_offload_e2e and output log
liudfgoo Jun 3, 2026
b7ff90f
revert to original summary
liudfgoo Jun 4, 2026
a5729fe
mount offloadstore on the context manager, exist at the same session …
liudfgoo Jun 4, 2026
4252520
update: score and filter
liudfgoo Jun 4, 2026
cae5f9d
construct handle and desc for offloadstore for large obs or model_output
liudfgoo Jun 4, 2026
6cf6308
build _ephemeral_system_messages bearing offloaded content
liudfgoo Jun 4, 2026
563e590
Ensure the injection of ephemeral messages across run scenarios.
liudfgoo Jun 4, 2026
27855f7
remove effective_* summary for offload
liudfgoo Jun 4, 2026
23c44e0
Add reload module mounting to achieve session-level reuse.
liudfgoo Jun 4, 2026
0eb6f31
Deduplicate to avoid repeated offloading of large outputs
liudfgoo Jun 4, 2026
d515148
update docstring
liudfgoo Jun 4, 2026
bad9b32
update test files for agent_context
liudfgoo Jun 5, 2026
bdc3e43
update test_cm_writing
liudfgoo Jun 5, 2026
fb3d544
delete irrelevant files
liudfgoo Jun 5, 2026
b38b13b
offload test results
liudfgoo Jun 5, 2026
ab8d8b0
delete .claude
liudfgoo Jun 5, 2026
aae8be2
remove outdated doc
liudfgoo Jun 5, 2026
d22a6c8
update note for feat/opt-agent-context-refactor
liudfgoo Jun 5, 2026
63be55d
solving conflict
liudfgoo Jun 12, 2026
e037c0e
Resolve some merge conflict files.
liudfgoo Jun 15, 2026
b6f1279
chore: strip unrelated backend/Context Components, exclude temp_scripts
liudfgoo Jun 16, 2026
a14d41c
chore: remove old monolithic agent_context.py (replaced by agent_cont…
liudfgoo Jun 16, 2026
275e80b
docs: tighten offload parameter docs in summary_config.py
liudfgoo Jun 16, 2026
3ed0c1b
resolve merge conflict in core_agent.py with upstream/develop
liudfgoo Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,7 @@ sdk/benchmark/.env
.pytest-tmp
doc/mermaid

.claude/skills/python-import-triage
.claude/skills/python-import-triage

# Debug scripts preserved on feat/opt-agent-context-temp-scripts
sdk/nexent/core/agents/temp_scripts/
1 change: 1 addition & 0 deletions sdk/nexent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .memory import *
from .storage import *
from .vector_database import *
from .container import *
from .skills import *


Expand Down
1,409 changes: 0 additions & 1,409 deletions sdk/nexent/core/agents/agent_context.py

This file was deleted.

29 changes: 29 additions & 0 deletions sdk/nexent/core/agents/agent_context/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Agent context management for memory compression and summarization.

Provides ContextManager for token-aware memory compression of agent memory,
supporting incremental summarization with cache-based optimization.
"""

from .manager import ContextManager
from .offload_store import OffloadStore
from .summary_step import SummaryTaskStep
from .llm_summary import format_summary_output, _is_context_length_error
from .step_renderer import compress_history_offline

# Re-export types from sibling modules so that
# ``from agent_context import ContextManagerConfig`` still works.
from ..summary_config import ContextManagerConfig
from ..summary_cache import CompressionCallRecord, PreviousSummaryCache, CurrentSummaryCache

__all__ = [
"ContextManager",
"OffloadStore",
"SummaryTaskStep",
"format_summary_output",
"_is_context_length_error",
"compress_history_offline",
"ContextManagerConfig",
"CompressionCallRecord",
"PreviousSummaryCache",
"CurrentSummaryCache",
]
215 changes: 215 additions & 0 deletions sdk/nexent/core/agents/agent_context/budget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
"""Budget estimation, trimming, and pure data helpers for ContextManager."""

import hashlib
import logging
from typing import Callable, List, Optional, Tuple

from smolagents.memory import ActionStep, MemoryStep, TaskStep

from ...utils.token_estimation import estimate_tokens_text
from ..summary_cache import PreviousSummaryCache, CurrentSummaryCache

logger = logging.getLogger("agent_context.budget")


# ============================================================
# Pure data helpers (no dependencies beyond stdlib + types)
# ============================================================

def extract_pairs(steps: List[MemoryStep]) -> List[tuple]:
"""Extract (TaskStep, ActionStep) pairs from a step list."""
pairs = []
i = 0
from .summary_step import SummaryTaskStep
while i < len(steps):
if isinstance(steps[i], TaskStep) and not isinstance(steps[i], SummaryTaskStep):
if i + 1 < len(steps) and isinstance(steps[i + 1], ActionStep):
pairs.append((steps[i], steps[i + 1]))
i += 2
continue
i += 1
return pairs


def action_content(action: ActionStep) -> str:
"""Extract the output text from an ActionStep."""
return action.action_output or getattr(action, "output", "") or ""


def pair_fingerprint(task_content: str, action_content: str) -> str:
"""Compute a fingerprint hash for a (task, action) pair."""
raw = (task_content[-200:] + action_content[-200:])
return hashlib.md5(raw.encode()).hexdigest()


def action_fingerprint(action: ActionStep) -> str:
"""Compute a fingerprint hash for an ActionStep."""
raw = (
str(action.step_number or "")
+ (action.model_output or "")[-200:]
+ (
action.action_output if isinstance(action.action_output, str)
else str(action.action_output) if action.action_output else ""

Check warning on line 52 in sdk/nexent/core/agents/agent_context/budget.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional expression into an independent statement.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ7PMs8_cUJAa9XO-Ak8&open=AZ7PMs8_cUJAa9XO-Ak8&pullRequest=3243
)[-200:]
)
return hashlib.md5(raw.encode()).hexdigest()


def has_invoked_tools(action: ActionStep) -> bool:
"""Check whether an ActionStep invokes any registered tool.

Unlike ``is_tool_call_step()`` which only checks for the generic
``tool_calls is not None`` (always True for CodeAgent steps), this
function checks the ``invoked_tools`` list for actual tool names.

Returns True only when the step's code called at least one tool
that is registered in the agent's ``self.tools`` dict.
"""
invoked = getattr(action, "invoked_tools", None)
return bool(invoked)


def is_observation_step(action: ActionStep) -> bool:
"""Check if an ActionStep is an observation step."""
return action is not None and hasattr(action, 'observations') and action.observations is not None


def is_tool_call_step(action: ActionStep) -> bool:
"""Check if an ActionStep is a tool call step."""
return action is not None and hasattr(action, 'tool_calls') and action.tool_calls is not None


# ============================================================
# Cache validation (depends on fingerprint functions, pure)
# ============================================================

def is_prev_cache_valid(
prev_pairs: List[tuple], cache: Optional[PreviousSummaryCache],
) -> Tuple[bool, int]:
"""Check whether the previous cache covers a prefix of prev_pairs.

Returns (is_valid, covered_idx). When is_valid is True,
prev_pairs[0:covered_idx] can be replaced by cache.summary_text,
and prev_pairs[covered_idx:] represents the uncovered incremental portion.
"""
if cache is None or not prev_pairs:
return False, 0
if cache.covered_pairs == 0 or cache.covered_pairs > len(prev_pairs):
return False, 0
anchor_t, anchor_a = prev_pairs[cache.covered_pairs - 1]
fp = pair_fingerprint(anchor_t.task or "", action_content(anchor_a))
if fp != cache.anchor_fingerprint:
return False, 0
return True, cache.covered_pairs


def is_curr_cache_valid(
action_steps: List[ActionStep], cache: Optional[CurrentSummaryCache],
) -> Tuple[bool, int]:
"""Check whether the current cache covers a prefix of action_steps."""
if cache is None or not action_steps:
return False, 0
if cache.end_steps == 0 or cache.end_steps > len(action_steps):
return False, 0
anchor = action_steps[cache.end_steps - 1]
if action_fingerprint(anchor) != cache.anchor_fingerprint:
return False, 0
return True, cache.end_steps


# ============================================================
# Budget trimming (depends on render_fn for text conversion)
# ============================================================

def trim_pairs_to_budget(

Check failure on line 124 in sdk/nexent/core/agents/agent_context/budget.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ7PMs8_cUJAa9XO-Ak-&open=AZ7PMs8_cUJAa9XO-Ak-&pullRequest=3243
pairs: List[tuple], max_tokens: int,
render_fn: Callable[[List[tuple]], str],
keep_first: bool = True,
) -> List[tuple]:
"""Trim pairs to fit within a token budget.

Args:
pairs: List of (TaskStep, ActionStep) tuples.
max_tokens: Maximum token budget.
render_fn: Function to convert pairs to text (e.g. renderer.pairs_to_text).
keep_first: If True, always keep the first pair.
"""
if not pairs:
return []
pair_tokens = [
estimate_tokens_text(render_fn([p])) for p in pairs
]
sep = estimate_tokens_text("\n\n")
total = sum(pair_tokens) + sep * max(0, len(pairs) - 1)
if total <= max_tokens:
return list(pairs)

if keep_first and len(pairs) > 1:
budget = max_tokens - pair_tokens[0] - sep
kept_tail = []
for i in range(len(pairs) - 1, 0, -1):
cost = pair_tokens[i] + (sep if kept_tail else 0)
if cost > budget:
break
kept_tail.append(pairs[i])
budget -= cost
return [pairs[0]] + list(reversed(kept_tail))

budget = max_tokens
kept = []
for i in range(len(pairs) - 1, -1, -1):
cost = pair_tokens[i] + (sep if kept else 0)
if cost > budget:
break
kept.append(pairs[i])
budget -= cost
return list(reversed(kept)) if kept else [pairs[-1]]


def trim_actions_to_budget(
actions: List[ActionStep], task_text: str, max_tokens: int,
render_fn: Callable[[List[ActionStep]], str],
) -> List[ActionStep]:
"""Trim actions to fit within a token budget.

Args:
actions: List of ActionStep instances.
task_text: Task description text.
max_tokens: Maximum token budget.
render_fn: Function to convert actions to text (e.g. renderer.actions_to_text).
"""
if not actions:
return []

def _total_tokens(acts):
return estimate_tokens_text(task_text + render_fn(acts))

if _total_tokens(actions) <= max_tokens:
return list(actions)

for drop in range(1, len(actions) + 1):
remaining = actions[drop:]
if not remaining:
break
if is_observation_step(remaining[0]) and is_tool_call_step(actions[drop - 1]):
continue
if _total_tokens(remaining) <= max_tokens:
return list(remaining)

return _fallback_trim_actions(actions)


def _fallback_trim_actions(actions: List[ActionStep]) -> List[ActionStep]:
"""Fallback trimming that preserves the last complete tool call pair."""
if not actions:
return []
last_action = actions[-1]
if len(actions) >= 2 and is_observation_step(last_action):
prev_action = actions[-2]
if is_tool_call_step(prev_action):
logger.warning(
"Fallback limit triggered: Retaining the last complete ToolCall + Observation pair intact. "
"This may exceed the token budget, and downstream truncation will be relied upon."
)
return [prev_action, last_action]
return [last_action]
Loading
Loading