Skip to content

[Reborn] Wire SkillContextService into loop prompt path - #3476

Merged
serrrfirat merged 4 commits into
reborn-integrationfrom
reborn/issue-3473-skill-context-prod-20260511153002
May 11, 2026
Merged

serrrfirat merged 4 commits into
reborn-integrationfrom
reborn/issue-3473-skill-context-prod-20260511153002

Conversation

@serrrfirat

@serrrfirat serrrfirat commented May 11, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #3473
Stacked on #3470 (kb/kb-007).

Summary

  • Adds host-owned HostSkillContextSource and parser-backed snapshot builder in ironclaw_loop_support.
  • Reuses ironclaw_skills::parse_skill_md and SkillTrust to build SkillRunSnapshot for SkillContextService.
  • Wires selected skill instruction snippets into text-only prompt bundles and resolves them through the model port as system messages.
  • Binds snippet refs to ordinal + safe-summary hash so duplicate skill names stay distinct and source drift after prompt build fails closed.
  • Skips hidden/denied candidates before parsing raw SKILL.md, so invisible malformed skills cannot break prompt construction.
  • Adds RebornLoopDriverHostFactory::with_skill_context_source(...) so production composition can inject the real skill source without moving parsing into ironclaw_turns.

Tests

  • CARGO_TARGET_DIR=/tmp/ironclaw-target-3473 cargo fmt --all -- --check
  • CARGO_TARGET_DIR=/tmp/ironclaw-target-3473 cargo test -p ironclaw_loop_support --quiet
  • CARGO_TARGET_DIR=/tmp/ironclaw-target-3473 cargo test -p ironclaw_turns --quiet
  • CARGO_TARGET_DIR=/tmp/ironclaw-target-3473 cargo test -p ironclaw_reborn --quiet
  • CARGO_TARGET_DIR=/tmp/ironclaw-target-3473 cargo test -p ironclaw_architecture --quiet
  • CARGO_TARGET_DIR=/tmp/ironclaw-target-3473 cargo clippy -p ironclaw_turns -p ironclaw_loop_support -p ironclaw_reborn --all-targets -- -D warnings

Notes

  • This keeps ironclaw_turns as snapshot/prompt contract layer.
  • Production still must choose/provide the concrete HostSkillContextSource implementation for the active Reborn skill store.

@github-actions github-actions Bot added scope: dependencies Dependency updates size: L 200-499 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels May 11, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for skill instruction snippets within the agent loop by adding a HostSkillContextSource trait and integrating it into the context and model ports. The changes allow for parsing skill markdown, applying visibility and trust-based filtering, and materializing these snippets as system messages. Feedback suggests using .as_ref() when handling Option fields in the host factory to improve code robustness and avoid unnecessary cloning or potential move issues.

Comment on lines +125 to +127
if let Some(source) = self.skill_context_source.clone() {
context_adapter = context_adapter.with_skill_context_source(source);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Use .as_ref() on Option fields within a struct to prevent partial moves, making the code more robust against future changes.

Suggested change
if let Some(source) = self.skill_context_source.clone() {
context_adapter = context_adapter.with_skill_context_source(source);
}
if let Some(source) = self.skill_context_source.as_ref() {
context_adapter = context_adapter.with_skill_context_source(source.clone());
}
References
  1. Use .as_ref().map() on Option fields within a struct to prevent partial moves, making the code more robust against future changes.

Comment on lines +155 to +157
if let Some(source) = self.skill_context_source.clone() {
model_adapter = model_adapter.with_skill_context_source(source);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Use .as_ref() on Option fields within a struct to prevent partial moves, making the code more robust against future changes.

Suggested change
if let Some(source) = self.skill_context_source.clone() {
model_adapter = model_adapter.with_skill_context_source(source);
}
if let Some(source) = self.skill_context_source.as_ref() {
model_adapter = model_adapter.with_skill_context_source(source.clone());
}
References
  1. Use .as_ref().map() on Option fields within a struct to prevent partial moves, making the code more robust against future changes.

@serrrfirat
serrrfirat force-pushed the reborn/issue-3473-skill-context-prod-20260511153002 branch from 7e74526 to cdb676c Compare May 11, 2026 13:50
@serrrfirat
serrrfirat force-pushed the reborn/issue-3473-skill-context-prod-20260511153002 branch from cdb676c to c885be8 Compare May 11, 2026 14:14
@zmanian

zmanian commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Review

Summary: Wires SkillContextService into the Reborn text-only loop prompt path via a new host-owned HostSkillContextSource trait, converting SKILL.md files into ordered, hash-bound system-message snippets.

Findings:

  1. Tool attenuation gap — blocking. SkillTrust::Installed vs Trusted is plumbed into InstalledSkillSnapshot (skill_context.rs:194-208) and the project rule is that Installed skills must run with attenuated tool access. But this PR only feeds skills' prompt content into the model — there is no corresponding ToolDispatcher attenuation. Per CLAUDE.md "Everything Goes Through Tools," gating must happen at dispatch. Either confirm a follow-up wires SkillTrustLevel into the capability port / dispatch, or this PR lands a prompt-only feature where an Installed skill can still request any capability the loop exposes.

  2. SKILL.md prompt_content passes through unsanitized for Installed skills. parsed_skill_to_snapshot_entry (skill_context.rs:198-208) renders raw author-controlled markdown into the system message (test at line 1300 confirms "Use alpha prompt content." lands verbatim). For Trusted skills this is acceptable; for Installed/registry-sourced skills there is no fence-stripping or delimiter framing to prevent fake "user:" turns. Worth confirming whether SkillContextService sanitizes internally — not visible from this PR.

  3. Ordering: skill snippets precede identity messages. prompt.rs:170-189 prepends skill instructions before context.messages. The system prompt / identity files (AGENTS.md, SOUL.md per CLAUDE.md) arrive via context.messages — skill snippets thus precede the agent's own identity, inverting typical precedence (identity should dominate skill instructions). No test asserts ordering against an identity baseline.

  4. Snippet-ref derivation duplicated verbatim across loop_support/skill_context.rs:212-255 and turns/run_profile/prompt.rs:213-266 — same FNV constants, sanitize_ref_suffix, stable_snippet_ref_hash. The two must stay in lockstep for the snippet-ref binding check (lib.rs:644); drift silently breaks the "fail closed on source change" guarantee. Extract to a single location.

  5. Telemetry coverage missing. prompt_bundle_built milestone records no skill metadata. Without active-skill names + trust levels + ordinals, post-hoc debugging of "why did the model behave like skill X was active" is impossible. Add to the milestone.

Minor: FNV-1a is fine for dedup but a 64-bit non-cryptographic hash means a malicious skill author who controls safe_summary could craft a collision against another skill's ref. Low impact since the role check (lib.rs:646) also gates.

Verdict: Request changes — (1) and (4) are blockers; (2) and (3) need explicit answers.

@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels May 11, 2026
…omments-20260511204831

# Conflicts:
#	crates/ironclaw_turns/src/run_profile/skill_context.rs
Base automatically changed from kb/kb-007 to reborn-integration May 11, 2026 21:44
@serrrfirat
serrrfirat merged commit ef9c584 into reborn-integration May 11, 2026
15 checks passed
@serrrfirat
serrrfirat deleted the reborn/issue-3473-skill-context-prod-20260511153002 branch May 11, 2026 22:24
@zmanian

zmanian commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Post-merge re-review

3 of 5 prior findings resolved cleanly, 1 deferred to #3540, but 1 blocker still open and 1 new gap introduced:

Resolved

  • ✅ Installed prompt_content sanitization — parsed_skill_to_snapshot_entry (crates/ironclaw_loop_support/src/skill_context.rs:393-396) now drops prompt_content to None for SkillTrustLevel::Installed, with regression test skill_snapshot_builder_drops_installed_prompt_content_before_snapshot_storage (lines 569-591). Interim until [Reborn] Envelope installed skill prompt context #3540 lands the envelope.
  • ✅ Identity-before-skill ordering — LoopContextBundle.identity_messages: Vec<LoopContextMessage> added (line 1360); prompt.rs:170-189 extends in order identity → instruction snippets → messages. Test asserts the indices.
  • ✅ Snippet-ref derivation duplication — FNV constants and helpers live only in crates/ironclaw_turns/src/run_profile/skill_context.rs:1639-1740; loop_support re-exports. PR [Reborn] Centralize snippet display hashing #3507 further centralizes display hashing (separate refinement).
  • ✅ prompt_bundle_built skill metadata — milestone gained skill_context: Vec<PromptSkillContextMetadata> with {ordinal, source_name, trust_level}; populated from LoopContextSnippet.metadata (lines 1557-1570); hard-fails (AgentLoopHostErrorKind::Internal) if a skill:-prefixed snippet arrives without metadata. Good fail-closed posture.

Still open

  • ❌ Tool attenuation gap (original blocker Move whatsapp channel source to channels-src/ for consistency #1). SkillTrustLevel::Installed is plumbed through InstalledSkillSnapshot.trust and emitted as telemetry (turns/run_profile/skill_context.rs:1651), but never consulted by a ToolDispatcher or capability port. The code comment calls trust "used for telemetry and downstream attenuation checks" — implying attenuation is still follow-up. The new test text_only_host_skill_context_does_not_expand_capability_surface (lines 1256-1311) only asserts an already-empty surface stays empty; it does not exercise denial of a capability that would otherwise be visible. Per CLAUDE.md "Everything Goes Through Tools," dispatcher-side gating is the load-bearing enforcement and it doesn't exist yet.

  • ❌ identity_messages is a dangling contract (new gap). The field exists in LoopContextBundle and the prompt path consumes it, but ThreadBackedLoopContextPort::load_loop_context (crates/ironclaw_loop_support/src/lib.rs:98) hardcodes identity_messages: Vec::new(). Identity files (AGENTS.md/SOUL.md/USER.md) still flow through context.messages. So in production today the "identity before skill" ordering only holds by accident — if no skill snippets exist, or if identity files happen to appear earlier in thread history. Tests pass because RecordingAgentLoopHost fakes the population.

Filing a follow-up issue for both items. Recommend not relying on the skill-context path in production traffic until both are closed.

theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
…ntext-prod-20260511153002

[Reborn] Wire SkillContextService into loop prompt path
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: dependencies Dependency updates size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants