Skip to content

feat(resource): per-agent max_text_chars config + kb_diag_agent YAML align - #404

Closed
Million-mo wants to merge 1 commit into
refactor/391-eventbus-loopbackfrom
refactor/394-max-text-chars
Closed

feat(resource): per-agent max_text_chars config + kb_diag_agent YAML align#404
Million-mo wants to merge 1 commit into
refactor/391-eventbus-loopbackfrom
refactor/394-max-text-chars

Conversation

@Million-mo

Copy link
Copy Markdown
Collaborator

Replacement for #394 (original was merged but got caught in main rollback; #400 auto-merged into wrong base).

Per-agent max_text_chars config for resource truncation + kb_diag_agent YAML alignment.

10 files, +310/-47 — matches original #394 exactly.

Depends on #403. Review #403 first.

…align (#394)

* feat(resource): per-agent max_text_chars config + kb_diag_agent YAML align

ResourceConfig now accepts max_text_chars (default 10000, min 100) in
agent YAML config. NativeAgent creates a per-agent ResourceCapability
with the agent's max_text_chars instead of sharing the pool-level instance.

ResourceCapability.__init__ gains max_text_chars parameter (backward
compatible). Truncation suffix improved with guidance directing the model
to use narrower URIs or paginated read tools.

kb_diag_agent.yaml aligned with live knowledge_diag server v3.4.4:
- Enabled search_kb (removed from disabled_tools)
- Added get_doc_toc and read_chapter_page tool-schema-overlap rewrites
- Added search_kb rewrite with methods (FULL/FAST/WIKI) param docs
- Updated existing tool descriptions to reference page-based workflow

Supersedes PR #393.

* feat(mcp): resource subscribe-on-read wiring in McpServerCap

Best-effort subscribe to resource URIs after successful read_resource()
calls, enabling notifications/resources/updated for resources the agent
has read. Tracked subscriptions are re-established on reconnect and
cleaned up on disconnect.

No-op for servers with subscribe:false (like knowledge_diag v3.4.4) —
subscribe fails silently, read proceeds normally. Activates automatically
when server enables subscription support.

* fix(mcp): address PR #394 review — wire max_text_chars, fix broken test, consolidate truncation

- Wire self._max_text_chars into read_mcp_resource (was hardcoded
  _DEFAULT_READ_TEXT_LIMIT) and use _truncate_text helper with guidance
  suffix
- Remove dead _truncate_text static method from ResourceCapability
  (zero callers, old suffix format)
- Add constructor validation: max_text_chars < 100 raises ValueError
- Consolidate default constant: _DEFAULT_MAX_TEXT_CHARS in
  resource_resolver.py, aliased in resource_capability.py
- Fix broken test assertion in test_resource_resolution.py to match
  new guidance suffix format
- Add tests: max_text_chars validation, read_mcp_resource truncation
  with per-agent limit, suffix guidance text
- Correct changelog: limit was previously hardcoded, not a pre-existing
  constructor param
- Update capabilities/AGENTS.md: ResourceCapability is per-agent
  constructed, not registered at SESSION scope
@github-actions

Copy link
Copy Markdown

Review — per-agent max_text_chars + kb_diag_agent YAML align

Reviewed the full diff (refactor/391-eventbus-loopback...refactor/394-max-text-chars), the surrounding code paths, and the project conventions (root AGENTS.md, subsystem AGENTS.mds, tests/AGENTS.md, changelog rules). Delegated code and docs review to specialist subagents; findings below verified against the source.

Code — should-fix

1. McpServerCap reconnect re-subscribe is effectively dead codesrc/wolfharness/capabilities/mcp_server_cap.py:313-314, 322-338
self._client is only ever reset to None in __aexit__ (line 903), which also clears _subscribed_uris (line 906). There is no mid-lifecycle reconnect path that invalidates _client while preserving the set, so _resubscribe_all() only runs on first connect (empty set → no-op) or at teardown. The changelog's "re-established after reconnect" claim never fires. Either wire a real reconnect path or reword the changelog to mark this as forward-looking.

2. Subscribe-on-read is wired to the secondary read path, not the primary agent toolmcp_server_cap.py:517-523 vs 590-620
The agent-facing read_mcp_resource tool (resource_capability.py:739provider.read_mcp_resource()) does not subscribe; only the URI-based read_resource()/resolver path does. So the tool agents actually call never triggers resources/updated wiring. Inert today (kb_diag v3.4.4 declares no subscribe), but the feature as documented ("resources the agent has read") misses its main trigger. Move/duplicate the subscribe into read_mcp_resource, or route the tool through the resolver.

3. No tests for the new subscribe-on-read wiring or the per-agent construction — per tests/AGENTS.md, new behavior requires tests. The diff only covers ResourceCapability truncation/validation and the resolver suffix. Add unit tests for: subscribe fires once per URI after a successful read, subscribe failure is swallowed, _resubscribe_all re-subscribes on a fresh client, __aexit__ unsubscribes + clears the set — and for get_agentlet() constructing ResourceCapability(max_text_chars=...) per-agent.

4. OpenCode converter path ignores the per-agent limitsrc/wolfharness_server/opencode_server/converters.py:163-168
_resolve_resource() calls resolve_resource_content(...) without max_text_chars, so protocol-layer resource resolution always truncates at the 10_000 default while the agent tool honors max_text_chars. Before this PR both were consistently 10_000; the per-agent feature now makes them diverge. agent is in scope there — pass agent.config.resources.max_text_chars through.

Code — nits

  • Dead guard agent.py:1168resource_cap not in self._external_capabilities is always true for a freshly constructed instance (no __eq__). Harmless but misleading; remove it.
  • Constant drift risk_MIN_MAX_TEXT_CHARS = 100 (resource_capability.py:59) duplicates ge=100 (nodes.py:237); _DEFAULT_MAX_TEXT_CHARS = 10_000 (resource_resolver.py:31) duplicates default=10_000 (nodes.py:235), plus a third alias _DEFAULT_READ_TEXT_LIMIT. If the config bound changes, the cap's ValueError only fires at runtime in get_agentlet(). Consider one shared constant (importing from wolfharness_config is the sanctioned direction).
  • Private-name cross-module importresource_capability.py:41 imports _DEFAULT_MAX_TEXT_CHARS / _truncate_text from resource_resolver; now that truncation is a package-level utility, make it public (truncate_text).
  • _subscribed_uris unbounded on the shared pool-level cap — every distinct URI read by any session accumulates (and stays server-side-subscribed) until pool teardown; the check-then-add across an await can also double-subscribe under concurrent reads (idempotent, but worth a note).
  • Telemetry — the new subscribe/unsubscribe/_resubscribe_all methods do network I/O with no logfire span (mcp_server_cap.py currently has none). Notably __aexit__ now performs per-URI network calls on possibly already-closed transports — a new teardown cost. Spans on these paths would align with the telemetry rules.

Docs — should-fix

  • schema/config-schema.json not regeneratedmax_text_chars (and, pre-existing, the whole agent-level resources object) is missing from the committed machine-readable schema (last regenerated in feat(viking): auto-return image bytes from viking_read for vision models #356). The PR modifies config models; run uv run python scripts/generate_schema.py and commit the refresh. Not a CI breaker (examples don't set max_text_chars, and check-jsonschema tolerates unknown keys), but the natural moment to sync.
  • src/wolfharness/capabilities/AGENTS.md:30 over-broad — "ResourceCapability is constructed per-agent (not registered…)" is only half-accurate: the pool-scoped instance still exists (pool._setup_resource_capability(), pool.py:737) and is consumed by the OpenCode tool-listing routes (agent_routes.py:802-804, 853-855). Reword to name both construction sites so a contributor doesn't remove the pool instance and break those routes.

Docs — nits

  • docs/reference/core-concepts/toolsets.md:57-71 lists load_resource/get_resources as the resource_access tools, which don't exist (pre-existing staleness; natural to fix alongside this PR).
  • examples/kb_diag_agent.yaml:35-37 search_kb workflow sketch says to copy chapter_uriget_doc_toc(doc_uri), but get_doc_toc requires doc_uri = kb://documents/{document_id} (as its own 使用要求 states). Align the sketch.
  • No explanatory doc for the truncation behavior / suffix (the auto-generated config reference will surface the field; a short note in docs/explanation/capabilities.md or docs/how-to/configuration/mcp.md would help).

Positive

  • Changelog follows the YYYY-MM-DD-slug.md convention and documents all three changes accurately.
  • Good consolidation: dead ResourceCapability._truncate_text removed, both truncation sites now share one helper with the guidance suffix, covered by updated tests on both capability and converter sides.
  • Config field follows wolfharness_config conventions; fresh per-agent construction is cheap and safe (stateless cap, built once per Turn); no type suppressions / getattr / hasattr introduced.
  • capabilities/AGENTS.md edit also fixes a pre-existing inaccuracy (ResourceCapability was never actually registered at SESSION scope).

@github-actions

Copy link
Copy Markdown

Posted the synthesized review at #404 (comment).

Summary of what I reviewed and found (verified against source, with specialist code + docs subagents):

Code — should-fix

  1. Reconnect re-subscribe in McpServerCap is dead code — _client is only reset in __aexit__, which also clears the URI set, so _resubscribe_all never fires on a real reconnect.
  2. Subscribe-on-read is wired to the URI/resolver path, not the agent-facing read_mcp_resource tool agents actually use.
  3. No tests for the subscribe-on-read wiring or the new per-agent ResourceCapability construction in get_agentlet() (violates tests/AGENTS.md).
  4. OpenCode converter path (converters.py:163) still truncates at the fixed 10k default, diverging from per-agent max_text_chars.

Docs — should-fix
5. schema/config-schema.json not regenerated — new field (and the whole agent resources object) missing from the committed schema.
6. capabilities/AGENTS.md:30 over-broad — pool-scoped instance still exists and is used by OpenCode tool-listing routes.

Plus nits (dead guard, constant duplication, private-name import, unbounded _subscribed_uris, missing spans, stale toolsets.md tools list, one YAML workflow inconsistency). Positive: changelog follows convention, good truncation consolidation, fully typed with no suppressions.

New%20session%20-%202026-08-28T08%3A37%3A30.244Z
opencode session  |  github run

@Million-mo Million-mo closed this Aug 28, 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.

1 participant