perf: preserve conversation prefixes across agent turns

This commit is contained in:
chenmingxuan committed 2026-10-17 17:25:15 +08:00
1 parent 1440a1f508
commit f1bb5d02aa
19 files changed
+552 -205

No files matched your search

+21 -8
View File
@@ -18,9 +18,9 @@ Executes autonomous reactive mobile workflows through OpenAI Responses.
Conversation shape (shared with the Pro operator, history redesign §3.2):
the prompt is built every turn from a session :class:`TranscriptLedger` —
a byte-stable system prefix, the committed earlier turns (append-only; old UI
lists stripped at depth 1, screenshots resolved to visual summaries at depth
K, long spans chunk-compressed), and a fresh tail carrying the current
a byte-stable system prefix, the committed earlier turns (append-only within
a context phase; old observations and long spans compacted together only at
pressure thresholds), and a fresh tail carrying the current
observation under a ``# CURRENT OBSERVATION [T+mm:ss]`` header. Actions run
synchronously and their tool messages already carry the outcome; only a turn
whose action failed ends with an ``--- Action Execution Result (T+mm:ss) ---``
@@ -30,7 +30,7 @@ session recording.
Per-turn notices on the tail (the reasoning reminder after a silent turn, the
no-tool-call nudge, the final-turn warning, the user-guidance wrapper) are
marked ephemeral: the ledger's scrub edge deletes them once the message leaves
marked ephemeral: the ledger's compaction pass deletes them when it rewrites
the recent window, so they never reach the frozen history or a chunk capsule.
The verbatim body of a user instruction is the exception: it stays in the
active window until its turn is chunked, whose user lines then carry it on.
@@ -83,7 +83,12 @@ from artemis.llm.router import ModelEndpoint
from artemis.llm.structured import ParseFailure, parse_structured
from artemis.mcp.action_executor import McpActionExecutor
from artemis.mcp.observation import DeviceObservationError, require_observation
from artemis.memory.transcript import PRO_UI_LIST_MARKER, TranscriptLedger, mark_ephemeral
from artemis.memory.transcript import (
OBSERVATION_SCOPE_RULE,
PRO_UI_LIST_MARKER,
TranscriptLedger,
mark_ephemeral,
)
from artemis.services.llm import (
RobustChatModelWrapper,
acomplete,
@@ -272,6 +277,8 @@ class FlashRunner:
image_scrub_depth_relaxed=getattr(cfg, "image_scrub_depth_relaxed", None),
context_budget_tokens=getattr(cfg, "context_budget_tokens", None),
start_ratio=getattr(cfg, "start_ratio", None),
soft_ratio=getattr(cfg, "soft_ratio", 0.7),
hard_ratio=getattr(cfg, "hard_ratio", 0.9),
session_start=session_start if isinstance(session_start, (int, float)) else None,
)
if engine is not None:
@@ -1247,7 +1254,13 @@ class FlashRunner:
"""Installs the static prefix and captures the initial device state."""
ledger = self._build_ledger()
ledger.set_static_prefix(
[SystemMessage(content=self._render_system_prompt(tools_declaration))]
[
SystemMessage(
content=self._render_system_prompt(tools_declaration)
+ "\n"
+ OBSERVATION_SCOPE_RULE
)
]
)
# Capture Initial State (Screenshot + UI Tree)
@@ -1369,7 +1382,7 @@ class FlashRunner:
)
pending_notices = []
# S + F + A + tail: chunk compression and the scrub edge advance here.
# S + F + A + tail: history changes only at a compaction boundary.
messages = ledger.render([tail])
turn_base = len(messages) - 1
current_tools = report_only_tools if is_final else self._get_tools()
@@ -1427,7 +1440,7 @@ class FlashRunner:
# ratio — only from a provider measurement, never from the
# estimate (same as the Pro operator).
ledger.record_prompt_tokens(
step_token_usage.get("prompt_tokens"),
step_token_usage.get("prompt_tokens") if measured else None,
messages=messages[:-1] if measured else None,
)