Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
96c1317
fix prompts to honor skills catalog flag
Svector-anu Aug 25, 2026
4712668
fix: resolve skills catalog conflicts
senamakel Sep 12, 2026
77f6c97
chore: preserve workflow files
senamakel Sep 12, 2026
faf0ce6
fix(agent): pass skills catalog flag to prompt renderer
senamakel Sep 12, 2026
7760635
chore(ci): remove tinycortex-tests job and add skills catalog flag to…
senamakel Sep 12, 2026
727cbef
chore(ci): update tinymemory test module to v1.16.0 and add tinyconne…
senamakel Sep 12, 2026
aa5168c
feat(agent): expose skills catalog in orchestrator and skill executor
senamakel Sep 12, 2026
ce74c27
test: add `omit_skills_catalog` to test definitions and remove it fro…
senamakel Sep 12, 2026
3c17893
chore(orchestrator-tools): add omit_skills_catalog to test helper
senamakel Sep 12, 2026
b96d182
ci(workflows): add module-pin gate, e2e-test-support checks, and pin-…
senamakel Sep 12, 2026
5b57a81
fix(agent): avoid duplicate orchestrator skills catalog
senamakel Sep 12, 2026
5970d9b
test(render-helpers): add tests for skills catalog placement in syste…
senamakel Sep 12, 2026
cf0c884
chore(docs): remove stale doc comments from runner and sections
senamakel Sep 12, 2026
1525064
fix(test): remove unnecessary mut from PromptContext in test
senamakel Sep 12, 2026
1c06a21
docs(prompts): clarify installed skills catalog field
senamakel Sep 12, 2026
5a095f1
test(raw_coverage): add omit_skills_catalog to agent definitions in e…
senamakel Sep 12, 2026
d37ea2b
test(raw_coverage): add missing skills catalog flag to subagent rende…
senamakel Sep 12, 2026
187ecf8
fix(prompts): anchor skills catalog to workspace block
senamakel Sep 12, 2026
fb4252d
fix(ci): isolate full lib and bin test suites with single-threaded ex…
senamakel Sep 12, 2026
396e987
chore(ci): remove forced single-threaded test execution in full cover…
senamakel Sep 12, 2026
f1e3b52
fix(ci): isolate full lib and bin test suites with single-threaded ex…
senamakel Sep 12, 2026
0ea50ca
fix(ci): remove test-threads=1 from full coverage run
senamakel Sep 12, 2026
8df4dd6
fix(ci): run full lib and bin suites with single-threaded test execution
senamakel Sep 12, 2026
2626218
fix(ci): avoid forcing non-hermetic full coverage suite
senamakel Sep 12, 2026
81ef37f
chore: files changed scripts/ci/rust-coverage-changed.sh
senamakel Sep 12, 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
9 changes: 6 additions & 3 deletions scripts/ci/rust-coverage-changed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,11 @@ compile_raw_coverage_target() {
run_full() {
log "running FULL instrumented suite (reason: $1)"
llvm_cov clean --workspace
llvm_cov --no-report --no-fail-fast -p openhuman --lib
llvm_cov --no-report --no-fail-fast -p openhuman --bins
# The full lib/bin suites share process-global configuration, event-bus, and
# provider state. Keep them isolated just like the integration targets below
# so parallel libtest cases cannot leak state into one another.
llvm_cov --no-report --no-fail-fast -p openhuman --lib -- --test-threads=1
llvm_cov --no-report --no-fail-fast -p openhuman --bins -- --test-threads=1
while IFS= read -r target; do
[ -n "${target}" ] || continue
log "running full-suite integration target: ${target}"
Expand Down Expand Up @@ -407,7 +410,7 @@ llvm_cov clean --workspace
if [ "${#lib_filters[@]}" -gt 0 ]; then
log "running scoped lib unit tests with filters: ${lib_filters[*]}"
# libtest ORs multiple positional filters — one run covers all domains.
run_counted llvm_cov --no-report --no-fail-fast -p openhuman --lib -- "${lib_filters[@]}"
run_counted llvm_cov --no-report --no-fail-fast -p openhuman --lib -- "${lib_filters[@]}" --test-threads=1
fi

if [ "${#test_targets[@]}" -gt 0 ]; then
Expand Down
3 changes: 3 additions & 0 deletions src/openhuman/agent/harness/builtin_definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub(crate) fn test_main_def() -> AgentDefinition {
omit_identity: true,
omit_memory_context: true,
omit_safety_preamble: true,
omit_skills_catalog: true,
omit_profile: true,
omit_memory_md: true,
model: ModelSpec::Inherit,
Expand Down Expand Up @@ -105,6 +106,7 @@ pub(crate) fn test_inherit_echo_def() -> AgentDefinition {
omit_identity: true,
omit_memory_context: true,
omit_safety_preamble: true,
omit_skills_catalog: true,
omit_profile: true,
omit_memory_md: true,
model: ModelSpec::Inherit,
Expand Down Expand Up @@ -145,6 +147,7 @@ pub(crate) fn test_inherit_parallel_worker_def() -> AgentDefinition {
omit_identity: true,
omit_memory_context: true,
omit_safety_preamble: true,
omit_skills_catalog: true,
omit_profile: true,
omit_memory_md: true,
model: ModelSpec::Inherit,
Expand Down
4 changes: 4 additions & 0 deletions src/openhuman/agent/harness/definition_part_01.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ pub struct AgentDefinition {
#[serde(default = "defaults::true_")]
pub omit_safety_preamble: bool,

/// If `true`, the installed skills catalog is stripped from the prompt.
#[serde(default = "defaults::true_")]
pub omit_skills_catalog: bool,
Comment thread
senamakel marked this conversation as resolved.

/// If `true`, the user's `PROFILE.md` (generated by the onboarding
/// enrichment pipeline — LinkedIn scrape, etc.) is NOT injected into
/// the rendered prompt. Defaults to `true` so sub-agents stay lean:
Expand Down
1 change: 1 addition & 0 deletions src/openhuman/agent/harness/definition_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn make_def(id: &str) -> AgentDefinition {
omit_identity: true,
omit_memory_context: true,
omit_safety_preamble: true,
omit_skills_catalog: true,
omit_profile: true,
omit_memory_md: true,
model: ModelSpec::Inherit,
Expand Down
9 changes: 8 additions & 1 deletion src/openhuman/agent/harness/session/builder/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,11 +525,17 @@ impl Agent {
// body and get wrapped by [`SystemPromptBuilder::for_subagent`].
let mut prompt_builder = match target_def {
Some(def) => match &def.system_prompt {
PromptSource::Dynamic(build) => SystemPromptBuilder::from_dynamic(*build),
PromptSource::Dynamic(build) => {
SystemPromptBuilder::from_dynamic_with_skills_catalog(
*build,
!def.omit_skills_catalog,
)
}
PromptSource::Inline(text) => SystemPromptBuilder::for_subagent(
text.clone(),
def.omit_identity,
def.omit_safety_preamble,
!def.omit_skills_catalog,
),
PromptSource::File { path } => {
let prompt_root = config.workspace_dir.join("agent").join("prompts");
Expand Down Expand Up @@ -566,6 +572,7 @@ impl Agent {
body_text,
def.omit_identity,
def.omit_safety_preamble,
!def.omit_skills_catalog,
)
}
},
Expand Down
37 changes: 15 additions & 22 deletions src/openhuman/agent/harness/subagent_runner/ops/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use std::sync::Arc;
use std::time::Instant;

use crate::openhuman::agent::context::prompt::{
render_subagent_system_prompt_with_format, PromptContext, PromptTool, SubagentRenderOptions,
render_subagent_system_prompt_with_format_and_workflows, PromptContext, PromptTool,
SubagentRenderOptions,
};
use crate::openhuman::agent::file_state::with_file_state_agent_id;
use crate::openhuman::agent::harness::agent_graph::{AgentTurnRequest, AgentTurnUsage};
Expand Down Expand Up @@ -59,24 +60,7 @@ use super::provider::{
resolve_subagent_source, user_is_signed_in_to_composio, LazyToolkitResolver,
};

/// Runtime spawn-hierarchy gate decision for one delegation hop.
///
/// `parent_def` is the resolved parent agent definition (looked up from the
/// global registry by its definition id) or `None` when the parent can't be
/// resolved — e.g. a dynamically-named agent (model-council juror) or a custom
/// agent absent from the registry, or any context where the registry isn't
/// initialised. A `None` parent yields `Ok(())`: we skip rather than mask, the
/// same defensive posture the loader takes for unknown child ids.
///
/// A **worker** parent is also exempted. At runtime a worker only reaches the
/// spawn chokepoint via the documented collapsed `delegate_to_integrations_agent`
/// path (→ `integrations_agent`, itself a worker) — a shape the loader
/// intentionally leaves untouched. Re-denying it here would turn valid custom
/// worker agents that use `{ skills = "*" }` into runtime failures. The
/// worker-leaf authoring rule stays enforced statically at boot, and the
/// per-parent allowlist gate blocks any other worker spawn.
///
/// For chat / reasoning parents the hop is checked against
/// Validate the runtime spawn-hierarchy gate for one delegation hop.
/// [`validate_tier_transition`] (the single source of truth shared with the
/// boot loader walk); a forbidden hop is logged and becomes a
/// [`SubagentRunError::TierViolation`]. Logging lives here (rather than at the
Expand Down Expand Up @@ -1295,6 +1279,7 @@ async fn run_typed_mode(
definition.omit_safety_preamble,
definition.omit_profile,
definition.omit_memory_md,
definition.omit_skills_catalog,
);

let narrowed_integrations: Vec<crate::openhuman::agent::context::prompt::ConnectedIntegration> =
Expand Down Expand Up @@ -1400,14 +1385,21 @@ async fn run_typed_mode(

let system_prompt = match &definition.system_prompt {
PromptSource::Dynamic(build) => {
build(&prompt_ctx).map_err(|e| SubagentRunError::PromptLoad {
let mut body = build(&prompt_ctx).map_err(|e| SubagentRunError::PromptLoad {
path: format!("<dynamic:{}>", definition.id),
source: std::io::Error::other(e.to_string()),
})?
})?;
if !definition.omit_skills_catalog {
body.push_str("\n\n");
body.push_str(&crate::openhuman::agent::prompts::render_skills_catalog(
&parent.workflows,
));
}
body
}
PromptSource::Inline(_) | PromptSource::File { .. } => {
let archetype_prompt_body = load_prompt_source(&definition.system_prompt, &prompt_ctx)?;
render_subagent_system_prompt_with_format(
render_subagent_system_prompt_with_format_and_workflows(
Comment thread
senamakel marked this conversation as resolved.
&parent.workspace_dir,
&model,
&allowed_indices,
Expand All @@ -1417,6 +1409,7 @@ async fn run_typed_mode(
render_options,
parent.tool_call_format,
&narrowed_integrations,
&parent.workflows,
agents_md.global.as_deref(),
agents_md.local.as_deref(),
)
Expand Down
1 change: 1 addition & 0 deletions src/openhuman/agent/harness/subagent_runner/ops_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fn make_def_named_tools(names: &[&str]) -> AgentDefinition {
omit_identity: true,
omit_memory_context: true,
omit_safety_preamble: true,
omit_skills_catalog: true,
omit_profile: true,
omit_memory_md: true,
model: ModelSpec::Inherit,
Expand Down
1 change: 1 addition & 0 deletions src/openhuman/agent/library/ops_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ fn definition() -> AgentDefinition {
omit_identity: true,
omit_memory_context: true,
omit_safety_preamble: true,
omit_skills_catalog: true,
omit_profile: false,
omit_memory_md: true,
model: ModelSpec::Hint("reasoning".to_string()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ fn definition_with_tool_scope(
omit_identity: true,
omit_memory_context: true,
omit_safety_preamble: true,
omit_skills_catalog: true,
omit_profile: true,
omit_memory_md: true,
model: ModelSpec::Inherit,
Expand Down
47 changes: 22 additions & 25 deletions src/openhuman/agent/prompts/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ impl SystemPromptBuilder {
archetype_prompt_text: String,
omit_identity: bool,
omit_safety_preamble: bool,
include_skills_catalog: bool,
) -> Self {
let mut sections: Vec<Box<dyn PromptSection>> =
vec![Box::new(ArchetypePromptSection::new(archetype_prompt_text))];
Expand All @@ -143,12 +144,9 @@ impl SystemPromptBuilder {
if !omit_safety_preamble {
sections.push(Box::new(SafetySection));
}
// Skills catalogue and connected integrations are rendered by
// the individual agent's `prompt.rs` when that agent needs
// them (integrations_agent for the skill-executor voice,
// orchestrator/welcome for the delegator voice). The shared
// builder intentionally does not emit them — keeping
// agent-specific prose scoped to the agent that owns it.
if include_skills_catalog {
sections.push(Box::new(SkillsCatalogSection));
}
sections.push(Box::new(WorkspaceSection));

Self { sections }
Expand Down Expand Up @@ -182,26 +180,25 @@ impl SystemPromptBuilder {
pub fn from_dynamic(
builder: crate::openhuman::agent::harness::definition::PromptBuilder,
) -> Self {
Self {
sections: vec![
Box::new(DynamicPromptSection::new(builder)),
// Project instructions (AGENTS.md). The ~26 dynamic
// `agents/<id>/prompt.rs` builders (orchestrator / main chat,
// welcome, integrations_agent, …) hand-assemble their own body
// via the `render_*` helpers and none of them individually call
// `render_agents_md`, so the pre-loaded AGENTS.md layers on
// `PromptContext` would otherwise be silently dropped for the
// primary agent. Inject the shared section centrally here —
// mirroring how `build()` appends the grounding contract for all
// dynamic builders — so every dynamic agent inherits the same
// AGENTS.md injection as the `with_defaults` / `for_subagent`
// chains. Rendered after the agent's own body (as trailing
// standing guidance) and before the central grounding suffix.
// Empty (skipped) when neither layer carries content or the
// `agents_md_enabled` gate is off.
Box::new(AgentsInstructionsSection),
],
Self::from_dynamic_with_skills_catalog(builder, false)
}

/// Build a dynamic prompt and optionally append the shared skills catalog.
///
/// Dynamic built-in agents are normally responsible for assembling their
/// own prompt body, so the catalog gate must be carried from the agent
/// definition into this wrapper rather than inferred from the body.
pub fn from_dynamic_with_skills_catalog(
builder: crate::openhuman::agent::harness::definition::PromptBuilder,
include_skills_catalog: bool,
) -> Self {
let mut sections: Vec<Box<dyn PromptSection>> =
vec![Box::new(DynamicPromptSection::new(builder))];
if include_skills_catalog {
sections.push(Box::new(SkillsCatalogSection));
}
sections.push(Box::new(AgentsInstructionsSection));
Self { sections }
}

pub fn add_section(mut self, section: Box<dyn PromptSection>) -> Self {
Expand Down
7 changes: 4 additions & 3 deletions src/openhuman/agent/prompts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ pub use render_helpers::{
inject_snapshot_content, inject_workspace_file, inject_workspace_file_capped,
memory_date_label, render_agents_md, render_ambient_environment, render_datetime,
render_grounding, render_identity, render_runtime, render_safety,
render_subagent_system_prompt, render_subagent_system_prompt_with_format, render_tools,
render_user_files, render_user_identity, render_user_memory, render_user_reflections,
render_workspace, sync_workspace_file,
render_subagent_system_prompt, render_subagent_system_prompt_with_format,
render_subagent_system_prompt_with_format_and_workflows, render_tools, render_user_files,
render_user_identity, render_user_memory, render_user_reflections, render_workspace,
sync_workspace_file,
};

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion src/openhuman/agent/prompts/mod_tests_part_01_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fn grounding_contract_appended_to_every_build_path() {
assert!(defaults.contains(marker));

// 2. Sub-agent static chain.
let sub = SystemPromptBuilder::for_subagent("role".into(), true, true)
let sub = SystemPromptBuilder::for_subagent("role".into(), true, true, false)
.build(&ctx)
.unwrap();
assert!(sub.contains(marker));
Expand Down
11 changes: 9 additions & 2 deletions src/openhuman/agent/prompts/mod_tests_part_02_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn render_subagent_system_prompt_renders_workspace_tail() {
fn subagent_render_options_invert_definition_flags() {
// (omit_identity, omit_safety_preamble,
// omit_profile, omit_memory_md)
let options = SubagentRenderOptions::from_definition_flags(true, false, false, false);
let options = SubagentRenderOptions::from_definition_flags(true, false, false, false, true);
assert!(!options.include_identity);
assert!(options.include_safety_preamble);
assert!(options.include_profile);
Expand Down Expand Up @@ -110,6 +110,7 @@ fn render_subagent_system_prompt_honors_identity_safety_and_skills_flags() {
include_safety_preamble: true,
include_profile: false,
include_memory_md: false,
include_skills_catalog: false,
},
ToolCallFormat::Json,
&[],
Expand Down Expand Up @@ -193,6 +194,7 @@ fn render_subagent_system_prompt_injects_profile_md_even_when_identity_omitted()
include_safety_preamble: false,
include_profile: true,
include_memory_md: false,
include_skills_catalog: false,
},
ToolCallFormat::PFormat,
&[],
Expand Down Expand Up @@ -292,6 +294,7 @@ fn render_subagent_system_prompt_frames_memory_md_as_background() {
include_safety_preamble: false,
include_profile: false,
include_memory_md: true,
include_skills_catalog: false,
},
ToolCallFormat::PFormat,
&[],
Expand Down Expand Up @@ -340,6 +343,7 @@ fn render_subagent_system_prompt_omits_memory_framing_when_no_memory_content() {
include_safety_preamble: false,
include_profile: false,
include_memory_md: true,
include_skills_catalog: false,
},
ToolCallFormat::PFormat,
&[],
Expand Down Expand Up @@ -379,6 +383,7 @@ fn render_subagent_system_prompt_injects_profile_md_when_identity_included() {
include_safety_preamble: false,
include_profile: true,
include_memory_md: false,
include_skills_catalog: false,
},
ToolCallFormat::PFormat,
&[],
Expand Down Expand Up @@ -452,6 +457,7 @@ fn narrow_agent_with_omit_identity_still_loads_profile_md() {
true, // omit_safety_preamble
false, // omit_profile — opts IN to PROFILE.md
false, // omit_memory_md — opts IN to MEMORY.md too
true, // omit_skills_catalog
);

let tools: Vec<Box<dyn Tool>> = vec![Box::new(TestTool)];
Expand Down Expand Up @@ -498,7 +504,7 @@ fn narrow_subagent_definition_flags_skip_profile_md() {
.unwrap();

// Mirrors e.g. `critic/agent.toml` — all omit_* default-true.
let options = SubagentRenderOptions::from_definition_flags(true, true, true, true);
let options = SubagentRenderOptions::from_definition_flags(true, true, true, true, true);

let tools: Vec<Box<dyn Tool>> = vec![Box::new(TestTool)];
let rendered = render_subagent_system_prompt(
Expand Down Expand Up @@ -553,6 +559,7 @@ fn render_subagent_system_prompt_injects_memory_md_when_enabled() {
include_safety_preamble: false,
include_profile: false,
include_memory_md: true,
include_skills_catalog: false,
},
ToolCallFormat::PFormat,
&[],
Expand Down
3 changes: 3 additions & 0 deletions src/openhuman/agent/prompts/mod_tests_part_03_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fn profile_md_and_memory_md_are_capped_at_user_file_max_chars() {
include_safety_preamble: false,
include_profile: true,
include_memory_md: true,
include_skills_catalog: false,
},
ToolCallFormat::PFormat,
&[],
Expand Down Expand Up @@ -71,6 +72,7 @@ fn rendered_subagent_system_prompt_is_byte_stable_across_repeat_calls() {
include_safety_preamble: false,
include_profile: true,
include_memory_md: true,
include_skills_catalog: false,
};

let first = render_subagent_system_prompt(
Expand Down Expand Up @@ -166,6 +168,7 @@ fn for_subagent_builder_injects_user_files_even_when_identity_omitted() {
"You are a specialist agent.".into(),
true, // omit_identity — drops SOUL/IDENTITY preamble
true, // omit_safety_preamble
false,
);
let rendered = builder.build(&ctx).unwrap();

Expand Down
Loading
Loading