Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
7de19ea
feat(viking): add WikiBuildCapability for xeno-adp wiki build tools
Aug 11, 2026
cf372a4
feat(viking): add find_wiki + OPS/OPL worker tool permissions
Aug 11, 2026
0282766
feat(viking): wiki build tools, MCP PrefixedToolset, compaction, DCP …
Aug 13, 2026
a39a4a6
feat(viking): expose browse/browse_chapters to content roles
Aug 13, 2026
a5fa0d1
fix(viking): sanitize corrupted tool call args on session restore, ha…
Aug 15, 2026
b0703be
feat(viking): external expert OP submission capability
Aug 17, 2026
9a3d20b
refactor(viking): merge external_op into wiki_build tools pattern
Aug 17, 2026
635217b
feat(viking): grant OPS resolver tools + ops_dispatch_plan to wiki roles
Aug 17, 2026
f242c2e
feat(viking): support wiki tree upload with embeddings + bidirectiona…
Aug 18, 2026
c9009b5
feat(viking): expose OP flow as ticket tools, drop include_external_ops
Aug 18, 2026
f4cdaab
merge: merge fork/main (wolfharness upstream) into feat/viking-ops-op…
Aug 18, 2026
43eb8d6
fix(viking): move FunctionTool import to runtime, split live assertion
Aug 18, 2026
81bf76b
feat(team-mode): task lease tokens, member subtasks, model config, st…
Aug 18, 2026
79678f9
feat(config): support MCP tool prefixes
Aug 18, 2026
f878560
feat(openai): add openai_supports_strict_tool_definition model config
Aug 18, 2026
8317302
feat(wiki): expose OP tickets as resources + rename read_resource to …
Aug 18, 2026
1afdf5a
feat(openai): add openai_supports_tool_choice_required model config
Aug 18, 2026
d5410af
revert(openai): drop openai_supports_strict_tool_definition / tool_ch…
Aug 18, 2026
991e2b5
merge: merge origin/main #379 viking allowed-uri-prefixes into feat/v…
Aug 19, 2026
4023194
Add ACP Viking event archive
Aug 19, 2026
a1b5cdf
feat(viking, team-mode): OPS ticket update, active task capacity, CI …
Aug 19, 2026
270961c
Archive raw ACP protocol frames to Viking
Aug 19, 2026
80df573
Flush Viking protocol archive on ACP errors
Aug 19, 2026
2061910
test(viking): mark live e2e manual test as real_mcp
Aug 19, 2026
edc14d5
feat(team-mode): bind initial work before waking dynamic team members
Aug 19, 2026
89154b3
feat(viking): OPS/OPL ticket ops + wiki build tools for expert workflow
Aug 19, 2026
3db1fb0
style(viking): fix RUF003 fullwidth semicolon in comment (pass CI)
Aug 19, 2026
c566b99
Merge remote-tracking branch 'upstream/main' into feat/viking-ops-opl…
Aug 19, 2026
d57c00d
fix(viking): preserve full ACP memory traces
Aug 20, 2026
751476d
fix(config): expand env vars in MCP HTTP headers
Aug 11, 2026
5ce8025
feat(config): support MCP tool prefixes
Aug 18, 2026
e9efe6b
Merge branch 'feature/chen.bin.niko/iroot-viking-mem-diag' into main
Aug 20, 2026
47132bc
fix: satisfy ci checks for viking memory PR
Aug 20, 2026
f6b73c1
fix: address remaining pr ci failures
Aug 20, 2026
9ff989e
fix(ci): resolve ruff format + mypy failures on PR #366
Aug 20, 2026
4c52a3f
fix(ci): use attribute annotation for get_live_run to avoid MRO shado…
Aug 20, 2026
7a6c665
fix(ci): avoid shadowing live run lookup
Aug 20, 2026
684e42d
merge: integrate remote CI fix
Aug 20, 2026
19c54b0
fix(wiki-build): add read_source_packet to extraction worker tools, e…
Aug 20, 2026
e74a9f0
fix(viking): align acp archive with session transcripts
Aug 20, 2026
93848e5
fix(viking): update auto ingest unit expectations
Aug 20, 2026
70138ac
feat(viking): integrate wiki ticket tooling updates
Aug 20, 2026
1c49bb3
fix(viking): ticket + MCP manager + team comm hardening (pre-PR sync)
Aug 20, 2026
825bd7f
Merge branch 'feat/viking-ops-opl-worker-tools' into feature/wh/merge…
Aug 20, 2026
578550a
fix(tests): add missing SystemPromptPart import, dedupe UserPromptPart
Aug 20, 2026
83e59e3
Merge remote-tracking branch 'upstream/main' into feature/wh/merge-to…
Aug 21, 2026
19a2b5e
fix(ticket): guard skip_dedupe_lookup with TypeError fallback for old…
Aug 21, 2026
2dfdea9
Merge pull request #1 from coderlihong/feature/wh/merge-to-chenbin
ChenBinfighting1 Aug 21, 2026
a7ddf79
feat(wiki): standalone TicketEngine — decouple from xeno-adp-agentic
Aug 21, 2026
42856e3
Merge pull request #2 from coderlihong/feature/wh/merge-to-chenbin
ChenBinfighting1 Aug 21, 2026
4626491
Merge remote-tracking branch 'wolfharness/main' into feature/chen.bin…
Aug 21, 2026
48877c2
feat(wiki): decouple build/tools/ticket/index into standalone capabil…
Aug 21, 2026
fe48b9b
refactor(wiki): remove 18 dead tools from ALL_WIKI_TOOLS registry
Aug 21, 2026
42b0f94
Merge remote-tracking branch 'upstream/main' into feature/wh/merge-to…
Aug 21, 2026
be5133c
refactor(wiki): move WikiBuildTools implementation into capabilities/…
Aug 21, 2026
34a5f66
refactor(wiki): move WikiBuildTools implementation into capabilities …
Aug 21, 2026
55f1572
refactor(wiki): OP record format — readable filenames + trimmed YAML
Aug 21, 2026
693f74c
fix(ci): resolve ruff/mypy/test failures for PR #382
Aug 21, 2026
d2594f6
refactor(wiki): decouple capabilities/wiki from xeno-adp-agentic
Aug 21, 2026
c761922
fix: repoint _materialization_config_hash schema path to local templates
Aug 21, 2026
396cd75
fix(ticket): filter non-URI evidence entries from evidence_uris
Aug 21, 2026
8c482a2
refactor: move team orchestration capabilities into wiki/harness/
Aug 21, 2026
25eebd8
refactor(wiki): body-first relation readers — remove sync_frontmatter…
Aug 21, 2026
6cdde2d
feat(team): inject bounds into protocol_template format args
Aug 21, 2026
0617987
fix(team-comm): use live-run lookup + auto-release in shutdown_request
Aug 21, 2026
e909793
fix(wiki): advance checkpoint to relation_closure after build_relatio…
Aug 21, 2026
2f107df
fix(wiki): relax URI validation, fix OPA dedup, tighten conflict heur…
Aug 21, 2026
cde8aed
feat(wiki): P2+P3 — concurrency, OPA sharding, readability, cleanup
Aug 21, 2026
bec143c
feat(wiki): Component template branching, finalize gate core-path, ba…
Aug 22, 2026
ec9c38d
fix(wiki): audit cache staleness — force_refresh + entity-count guard
Aug 22, 2026
57c50d3
fix(wiki): Fault template branch, strip non-core checks, remove body …
Aug 22, 2026
0993451
fix(finalize): retry remote sync without re-auditing confirmed entities
Aug 22, 2026
39af3c2
fix(wiki): finalize entities optional + finalized_local receipt + dev…
Aug 22, 2026
fa8a120
fix(wiki): downgrade _reject_nonexistent_raw_sources to warning
Aug 23, 2026
9c489fa
fix(wiki): skip unresolvable sources in _current_source_snapshot_id
Aug 23, 2026
781bfcb
perf(wiki): batch relation closure + materialization conflict detection
Aug 23, 2026
3f7e034
fix(wiki): one OPA record per feedback submission; batch wiki WIP
Aug 24, 2026
b47620d
Merge remote-tracking branch 'chenbin/feature/chen.bin.niko/iroot-vik…
Aug 24, 2026
7ac0b66
fix(wiki): OPS/OPL external submissions always mint fresh records
Aug 24, 2026
1837fe1
Merge pull request #3 from coderlihong/feature/wh/merge-to-chenbin
ChenBinfighting1 Aug 24, 2026
8ea1955
fix(viking): broaden ticket reference scopes and evidence validation
Aug 24, 2026
4adaaef
fix(ci): resolve ruff/mypy/test failures for wiki capability
Aug 24, 2026
6198e82
fix(wiki): correct _format_index_block import path in before_model_re…
Aug 24, 2026
f2ba330
fix(ci): fix mypy attr-defined and wiki index import path
Aug 24, 2026
d81c7b8
fix(ci): remove unused viking test import
Aug 25, 2026
1642650
fix(viking): guard bulk relation writes
Aug 25, 2026
e7a91c6
merge: resolve conflicts with wolf1069b/main (PR #382 target)
Aug 25, 2026
80c08b9
fix(ticket): filter plain-text evidence via engine URI predicate
Aug 25, 2026
5fcb84e
fix(resources): allow agent-scoped URI scheme owners
Aug 25, 2026
7aef88f
fix(mcp): apply tool_prefix to get_capabilities toolset and allowed_t…
Aug 25, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Parse nested fallback model configurations

`FallbackModelConfig` now accepts discriminated model configuration objects from YAML, including
custom OpenAI-compatible endpoints. This makes configured fallback chains behave the same whether
their sub-models are supplied as Python objects or manifest dictionaries.

String model configurations can also declare `api_key_env`, allowing each custom endpoint to
resolve its own runtime credential without placing secret values in a manifest.
5 changes: 5 additions & 0 deletions changelog/unreleased/2026-08-12-team-member-role-lookup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Expose team member agent role lookup

`FileTeamState` now exposes the registered agent role behind a member display name. Harness
capabilities can use this read-only lookup to validate that specialized tasks are assigned to
members with the required capability set.
9 changes: 9 additions & 0 deletions changelog/unreleased/2026-08-12-viking-index-inject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Dynamic resource-namespace index injection

Added a dynamic `<openviking-index>` injection to `VikingCapability`,
cloning the existing profile-inject pattern. Four new config fields
(`index_enabled`, `index_max_tokens`, `index_limit`, `index_uri`) drive a
first-turn handler that lists live `viking://resources/<namespace>`
namespaces via the Viking SDK and injects them into the system prompt —
eliminating hard-coded namespace knowledge. The block format is a contract
consumed by the xeno-adp-agentic prompt layer; it is disabled by default.
14 changes: 14 additions & 0 deletions changelog/unreleased/2026-08-12-wiki-build-index-inject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Wiki build root index injection (B-scheme)

Added config-driven first-turn `<openviking-index>` injection to
`WikiBuildCapability`, parallel to the Viking capability's dynamic index
inject. `WikiBuildConfig` (and the entry-point `__init__` kwargs) gain
`index_enabled`, `index_max_tokens`, `index_limit`; a first-turn handler
lists the config-resolved `wiki`/`raw`/`bom` build roots (no server
enumeration, no viking tools) and injects them as a
`SystemPromptPart` before the latest user message. Disabled by default.
Also root-causes two pre-existing mypy errors: the lazy
`xeno_adp_agentic` import is suppressed via the correct
`xeno_adp_agentic.*` override, and the `RoleFilter.get_wrapper_toolset`
arg-type mismatch is fixed by typing the local toolset as
`FunctionToolset[Any]`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Use the stable wiki build service entry point

Updated `WikiBuildCapability` to lazily import the host writer from the stable
`xeno_adp_agentic.wiki.serve.build_tools` facade. The production capability
continues to execute in process and no longer couples AgentPool to the host's
legacy `mcp_server` module name. Public configuration and tool behavior are
unchanged.
6 changes: 6 additions & 0 deletions changelog/unreleased/2026-08-12-wiki-entity-batch-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Expose batched wiki entity materialization

Added `write_entities_batch` to the in-process wiki build capability's formal
write surface so extraction workers can validate and commit a bounded group of
new entities in one host call. Role filtering and write-tool classification
remain unchanged for existing tools.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Propagate team state directory to spawned members

Team sessions now pass the resolved `team_base_dir` into every initially
created or dynamically added member session. This keeps task-board and
blackboard consumers aligned with the lead when the runtime falls back to a
temporary team-state directory.

Team members are also prevented from moving a second task to `in_progress`
while another owned task is active. This preserves a single authoritative
current task for heartbeat, timeout, and reassignment logic; leads retain the
ability to repair task state explicitly.

Task ownership changes now follow the same authority boundary: members may
claim an unowned task only for themselves, while transfers between members
must be performed by the team lead. This prevents a worker with stale or
incorrect identity context from corrupting another worker's queue.

Leads can no longer shut down a member while it owns an `in_progress` task.
They must first explicitly reassign or cancel the active task, preventing
delayed status messages from terminating a worker that is still decoding or
writing an artifact. Pending work continues to produce the existing shutdown
warning so it can be reassigned normally.

Dynamic members now always receive the rendered team protocol, including
their exact `team_member_name`, before any caller-supplied task prompt. The
new member is also included in its initial roster. A custom prompt therefore
supplements the member identity instead of replacing it, avoiding workers
mistaking themselves for an earlier member that uses the same agent config.

Team messages now include a UTC `sent_at` timestamp, and the protocol requires
destructive coordination decisions to verify newer task state. In addition,
`shutdown_request` rejects members with an active run, closing the gap where a
worker could be killed during a storage call before its task heartbeat reached
`in_progress`.
11 changes: 11 additions & 0 deletions changelog/unreleased/2026-08-18-configurable-mcp-tool-prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Configurable MCP Tool Prefixes

MCP server and Viking capability configuration can now declare an explicit
`tool_prefix` for model-visible tool names. This gives deployments with multiple
knowledge sources a stable, business-owned namespace such as `fault_kb_search`
or `manuals_read`, instead of relying only on automatic display-name-derived
prefixes.

For MCP capability wrappers, `enabled_tools` is adjusted to match the prefixed
tool names exposed to the model. This keeps allow-lists aligned with the actual
toolset after namespacing.
11 changes: 11 additions & 0 deletions changelog/unreleased/2026-08-19-atomic-team-member-task-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: Bind initial work before waking dynamic team members
type: fixed
---

`team_add_member` now accepts either `initial_task` or `initial_task_id`. The
team task is persisted and assigned before the new member receives its first
message, preventing dynamic workers from observing an empty `mine_only` task
view and exiting before dispatch completes. Recovery can bind a released
pending or blocked task to a replacement member without creating a duplicate
task ID.
3 changes: 3 additions & 0 deletions changelog/unreleased/2026-08-20-viking-trace-reliability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Viking Trace Reliability

Improved Viking trace capture by aligning ACP archival with readable session transcripts instead of raw protocol-frame logs, keeping ACP transcripts untrimmed by default, retrying captured auto-ingest batches after failures, preserving full tool outputs, extracting text from structured user prompts, and keeping injected system context out of memory-ingested user messages.
20 changes: 20 additions & 0 deletions changelog/unreleased/2026-08-25-filter-plain-text-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Filter plain-text evidence entries before OPA/OPS/OPL evidence_uris

`_ticket_evidence` blindly merged `cited_references[].uri` with every
`evidence` entry. `evidence` is a free-form user expression: it may carry
provider URIs or plain audit text (e.g. `QuotedText: ...` /
`Matched knowledge snippet: ...`). Plain text reaching the engine's
`_validate_opa_uris` raised

OPA URI must be a provider resource URI or local wiki/raw URI,
got 'QuotedText: ...'.

and failed the whole external OPA/OPS ticket submission as
`pending_external_submission`.

Each engine now exposes `is_valid_op_uri` (the same provider/wiki/raw
format rule enforced by `_validate_opa_uris`, extracted so the check is
reusable). The ticket closures capture that predicate and pass it to
`_ticket_evidence`, which keeps `cited_references[].uri` verbatim and only
admits `evidence` entries that pass it. When an engine does not expose the
predicate, the historical blind merge is preserved.
6 changes: 5 additions & 1 deletion docs/rfcs/draft/RFC-0055-dynamic-team-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -989,9 +989,13 @@ delete_blackboard(key: str) -> str
shutdown_request(member_name: str) -> str
"""Request shutdown of a specific teammate."""

team_add_member(name: str, agent: str, prompt: str = "", lifecycle: str = "persistent", notify: str = "", instructions: str = "", skills: list[str] | None = None) -> str
team_add_member(name: str, agent: str, prompt: str = "", lifecycle: str = "persistent", notify: str = "", instructions: str = "", skills: list[str] | None = None, initial_task: InitialMemberTask | None = None, initial_task_id: str = "") -> str
"""Add a new member to an existing team."""
# skills: optional — same semantics as team_create member dict "skills" key
# initial_task: create and owner-bind an authoritative task before the
# member's first wakeup
# initial_task_id: owner-bind an existing released pending/blocked task
# before wakeup; mutually exclusive with initial_task

task_create_batch(tasks: list[dict]) -> str
"""Create multiple tasks atomically with inter-task dependencies."""
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,7 @@ tiktoken = ["tiktoken"] # Exact token counting
tts = [
"anyvoice[tts-edge,openai]>=0.0.2"
] # Text-to-Speech using AnyVoice (disabled: cffi doesn't support free-threaded 3.13)
viking = [
"openviking-sdk>=0.1.6"
] # Viking storage & retrieval capability (download_bytes for image vision)
viking = ["openviking-sdk>=0.1.8"] # Viking storage & retrieval capability (download_bytes for image vision)
watchdog = ["watchdog>=4.0.0"] # Filesystem watcher for skill hot-reload
zed = ["zstandard>=0.23.0"] # Zed IDE storage provider

Expand Down Expand Up @@ -387,7 +385,7 @@ python_version = "3.13"
pretty = true
strict = true
check_untyped_defs = true
exclude = ["venv/", ".venv/"]
exclude = ["venv/", ".venv/", "src/wolfharness/capabilities/wiki/"]
plugins = ["pydantic.mypy"]
warn_return_any = true
warn_unused_configs = true
Expand Down Expand Up @@ -445,6 +443,8 @@ module = [
"fastmcp.tools.tool",
"apprise",
"openviking_sdk",
"xeno_adp_agentic.*",
"xeno_adp_harness.*",
]
ignore_missing_imports = true

Expand Down
33 changes: 33 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ max-complexity = 15
"TRY300",
"PLR0915"
]
# Viking ticket tools: ctx is pydantic-ai internal, reward-complex closures,
# BLE001 is intentional for best-effort remote sync
"src/wolfharness/capabilities/viking/ticket.py" = ["D417", "BLE001", "PLR0915"]
# Viking capability: BLE001 for safe SkillResource defaults, TRY300 for early-return pattern
"src/wolfharness/capabilities/viking/__init__.py" = ["BLE001", "TRY300"]
# Viking instructions: E501 for markdown table lines
Expand Down Expand Up @@ -187,5 +190,35 @@ max-complexity = 15
"TRY301",
]

# Wiki capability (copied from xeno-adp-agentic): CJK docstrings (RUF001/002/003),
# long lines (E501), magic values (PLR2004), complex methods (PLR0915/PLR0911),
# import ordering (E402), blind except for best-effort ops (BLE001),
# raise-in-try (TRY301), docstring style (D205/D417),
# mutable defaults (RUF012), manual list comprehension (PERF401),
# collapsible if (SIM102)
"src/wolfharness/capabilities/wiki/**" = [
"RUF001",
"RUF002",
"RUF003",
"E501",
"PLR2004",
"PLR0915",
"PLR0911",
"E402",
"BLE001",
"TRY301",
"D205",
"D417",
"RUF012",
"PERF401",
"SIM102",
"EXE002",
"PLW0127",
"PTH123",
"PLR1704",
"PLW2901",
"TRY004",
]

[format]
preview = true
6 changes: 6 additions & 0 deletions src/wolfharness/agents/native_agent/turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ async def execute(self) -> AsyncGenerator[RichAgentStreamEvent[Any]]: # noqa: P
):
from wolfharness.observability.trace import get_trace_id

# Sanitize any inbound history (restored sessions can carry
# poisoned tool call args that make the first model request fail
# with HTTP 400 before this turn's own CallToolsNode path runs).
if self._message_history_input:
sanitize_tool_call_args_in_messages(self._message_history_input)

logfire.info(
"Turn started",
trace_id=get_trace_id(),
Expand Down
89 changes: 89 additions & 0 deletions src/wolfharness/capabilities/dcp/capability.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
from pydantic_ai.models import ModelRequestContext
from pydantic_ai.tools import ToolDefinition

from wolfharness.messaging.message_history import MessageHistory

_DCP_METADATA_KEY = "dcp"

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -185,6 +187,7 @@ def __init__(
protected_tools: set[str] | None = None,
meta_tool_retention: int = 1,
clear_thinking_enabled: bool = False,
auto_compact_on_critical: bool = False,
) -> None:
"""Initialize the dynamic context pruning capability.

Expand Down Expand Up @@ -217,6 +220,9 @@ def __init__(
on the prune tool is active. When ``True``, the model can
toggle persistent stripping of ``ThinkingPart`` content from
assistant messages before the last user message.
auto_compact_on_critical: When ``True`` and the watermark reaches
CRITICAL, run the manifest ``compaction`` pipeline on the
persistent conversation once per episode.
"""
self._config: DCPConfig = DCPConfig(
enabled=enabled,
Expand All @@ -238,6 +244,7 @@ def __init__(
protected_tools=protected_tools if protected_tools is not None else set(),
meta_tool_retention=meta_tool_retention,
clear_thinking_enabled=clear_thinking_enabled,
auto_compact_on_critical=auto_compact_on_critical,
)
self._watermark: WatermarkStateMachine = WatermarkStateMachine(
info_threshold=self._config.info_threshold,
Expand Down Expand Up @@ -289,6 +296,7 @@ def from_config(cls, config: DCPConfig) -> DynamicContextPruningCapability:
protected_tools=config.protected_tools,
meta_tool_retention=config.meta_tool_retention,
clear_thinking_enabled=config.clear_thinking_enabled,
auto_compact_on_critical=config.auto_compact_on_critical,
)

# ---- DCPState access ----
Expand Down Expand Up @@ -585,6 +593,23 @@ async def before_model_request( # noqa: PLR0915
state.watermark_level = level
state.current_tokens = total_tokens

# Re-arm the CRITICAL compactor once pressure drops below CRITICAL.
# ``_compact_on_critical`` sets ``critical_compacted`` permanently at
# first CRITICAL, so without this reset it would only ever compact
# ONCE per session — leaving every subsequent context overflow
# unhandled. A sawtooth (rise -> compact -> fall -> rise) must
# trigger a fresh compaction each cycle.
if level < WatermarkLevel.CRITICAL and state.critical_compacted:
state.critical_compacted = False
logger.info(
"DynamicContextPruning Phase 0: re-armed CRITICAL compactor "
"(turn=%d watermark=%s tokens=%d/%d)",
state.current_turn,
level.name,
total_tokens,
self._config.max_context_tokens,
)

logger.info(
"DynamicContextPruning Phase 0: turn=%d msgs=%d tokens=%d/%d (%.1f%%) "
"watermark=%s (estimated=%d last_real=%d new_msgs=%d delta=%d)",
Expand Down Expand Up @@ -803,6 +828,16 @@ async def before_model_request( # noqa: PLR0915
self._config.nudge_step_frequency,
)

# --- Phase 5: Auto-compact on CRITICAL watermark ---
# Opt-in (auto_compact_on_critical). Runs the manifest ``compaction``
# pipeline on the persistent conversation so the next turn seeds from
# compacted history — DCP's own request-context edits are ephemeral.
if (
self._config.auto_compact_on_critical
and state.watermark_level >= WatermarkLevel.CRITICAL
):
await self._compact_on_critical(ctx, messages)

# --- Pipeline summary ---
# Store cumulative for next iteration's delta calculation.
state.last_cumulative_input_tokens = current_cumulative
Expand Down Expand Up @@ -834,6 +869,60 @@ async def before_model_request( # noqa: PLR0915

return dataclasses.replace(request_context, messages=messages)

async def _compact_on_critical(
self,
ctx: RunContext[AgentContext],
request_messages: list[ModelMessage],
) -> None:
"""Run the manifest compaction pipeline on the persistent conversation.

Priorities: (1) the agent's persistent ``conversation`` when the node
is a native agent, (2) otherwise the request snapshot. Fires at most
once per CRITICAL episode to avoid repeated summarization every turn.

Args:
ctx: The pydantic-ai run context carrying the agent/state.
request_messages: The current request message snapshot (fallback).
"""
host_ctx = ctx.deps.node.host_context
pipeline = host_ctx.manifest.get_compaction_pipeline() if host_ctx is not None else None
if pipeline is None:
logger.debug("DynamicContextPruning Phase 5: no manifest compaction pipeline, skip")
return

state = self._get_dcp_state(ctx)
if state.critical_compacted:
return

conversation: MessageHistory | None = None
try:
conversation = ctx.deps.native_agent.conversation
except AssertionError:
conversation = None

if conversation is not None:
from wolfharness.messaging.compaction import compact_conversation

_, compacted_count = await compact_conversation(pipeline, conversation)
logger.info(
"DynamicContextPruning Phase 5: compacted persistent conversation "
"(turn=%d msgs=%d watermark=%s)",
state.current_turn,
compacted_count,
state.watermark_level.name,
)
else:
compacted_messages = await pipeline.apply(request_messages)
logger.info(
"DynamicContextPruning Phase 5: compacted request snapshot "
"(turn=%d msgs=%d→%d watermark=%s)",
state.current_turn,
len(request_messages),
len(compacted_messages),
state.watermark_level.name,
)
state.critical_compacted = True

# ---- Post-tool hook ----

async def after_tool_execute(
Expand Down
Loading
Loading