13#include <c4/std/string.hpp>
24namespace entropic::prompts {
33static std::string
trim(
const std::string& s)
35 auto start = s.find_first_not_of(
" \t\n\r");
36 if (start == std::string::npos) {
39 auto end = s.find_last_not_of(
" \t\n\r");
40 return s.substr(start, end - start + 1);
59 const std::string& content,
60 const std::filesystem::path& path,
64 if (content.substr(0, 3) !=
"---") {
65 return "prompt file " + path.string()
66 +
" missing YAML frontmatter";
69 auto second_delim = content.find(
"---", 3);
70 if (second_delim == std::string::npos) {
71 return "prompt file " + path.string()
72 +
" has malformed frontmatter";
75 std::string yaml_block = content.substr(3, second_delim - 3);
76 body =
trim(content.substr(second_delim + 3));
78 tree = ryml::parse_in_arena(
79 ryml::to_csubstr(path.string()),
80 ryml::to_csubstr(yaml_block));
94 static constexpr const char* names[] = {
"constitution",
"app_context",
"identity"};
95 int idx =
static_cast<int>(type);
96 return (idx >= 0 && idx <= 2) ? names[idx] :
"unknown";
109 const std::string& type_str,
const std::filesystem::path& path,
112 if (type_str ==
"constitution") {
113 t = PromptType::CONSTITUTION;
114 }
else if (type_str ==
"app_context") {
115 t = PromptType::APP_CONTEXT;
116 }
else if (type_str ==
"identity") {
117 t = PromptType::IDENTITY;
119 err =
"prompt file " + path.string()
120 +
" has unknown type '" + type_str +
"'";
135 const std::filesystem::path& path,
141 auto content = read_file(path);
142 if (content.empty()) {
143 err =
"cannot read prompt file: " + path.string();
151 ryml::ConstNodeRef root;
152 std::string type_str;
154 root = tree.rootref();
155 if (!extract(root,
"type", type_str)) {
156 err =
"prompt file " + path.string()
157 +
" missing 'type' field";
166 if (err.empty() && actual_type != expected_type) {
167 err =
"prompt file " + path.string() +
" has type '"
168 + type_str +
"' but was loaded as '"
173 result.
type = actual_type;
174 extract(root,
"version", result.
version);
190 if (!root.has_child(
"phases") || !root[
"phases"].is_map()) {
194 for (
auto child : root[
"phases"]) {
196 std::string phase_name = to_string(child.key());
201 extract_string_list_opt(child,
"bash_commands", phase.
bash_commands);
202 (*fm.
phases)[phase_name] = std::move(phase);
216 if (!root.has_child(
"benchmark") || !root[
"benchmark"].is_map()) {
220 auto bench = root[
"benchmark"];
221 if (!bench.has_child(
"prompts") || !bench[
"prompts"].is_seq()) {
224 for (
auto p_node : bench[
"prompts"]) {
226 extract(p_node,
"prompt", bp.
prompt);
227 if (p_node.has_child(
"checks") && p_node[
"checks"].is_seq()) {
228 for (
auto c_node : p_node[
"checks"]) {
229 std::string check_yaml;
230 ryml::emitrs_yaml(c_node, &check_yaml);
234 fm.
benchmark->prompts.push_back(std::move(bp));
262 if (extract(root,
"top_k", ival)) { fm.
top_k = ival; }
264 if (extract(root,
"temperature", fval)) { fm.
temperature = fval; }
265 if (extract(root,
"top_p", fval)) { fm.
top_p = fval; }
266 if (extract(root,
"min_p", fval)) { fm.
min_p = fval; }
267 if (extract(root,
"presence_penalty", fval)) { fm.
presence_penalty = fval; }
269 if (extract(root,
"repeat_penalty", fval)) { fm.
repeat_penalty = fval; }
273 if (extract(root,
"enable_thinking", think)) { fm.
enable_thinking = think; }
287 extract(root,
"routable", fm.
routable);
304 fm.
type = PromptType::IDENTITY;
305 extract(root,
"version", fm.
version);
306 extract(root,
"name", fm.
name);
307 extract_string_list(root,
"focus", fm.
focus);
308 extract_string_list(root,
"examples", fm.
examples);
310 std::string grammar_str;
311 if (extract(root,
"grammar", grammar_str)) {
314 std::string auto_chain_str;
315 if (extract(root,
"auto_chain", auto_chain_str)) {
319 extract_string_list_opt(root,
"allowed_tools", fm.
allowed_tools);
320 extract_string_list_opt(root,
"bash_commands", fm.
bash_commands);
337 const std::filesystem::path& path,
342 auto content = read_file(path);
343 if (content.empty()) {
344 err =
"cannot read identity file: " + path.string();
352 ryml::ConstNodeRef root;
354 root = tree.rootref();
357 std::string type_str;
358 if (!extract(root,
"type", type_str) || type_str !=
"identity") {
359 err =
"prompt file " + path.string()
360 +
" is not an identity file (type='"
369 err =
"identity " + path.string()
370 +
": focus must have at least one entry";
372 err =
"identity " + path.string()
373 +
": name must not be empty";
391 const std::optional<std::filesystem::path>& constitution_path,
393 const std::filesystem::path& data_dir,
399 s_log->info(
"Constitution disabled by config");
402 std::filesystem::path path = constitution_path.has_value()
404 : data_dir /
"prompts" /
"constitution.md";
406 if (!std::filesystem::exists(path)) {
407 err =
"constitution file not found: " + path.string();
416 body = std::move(result.
body);
417 s_log->info(
"Constitution loaded from {}", path.string());
435 const std::optional<std::filesystem::path>& app_context_path,
437 const std::filesystem::path& data_dir,
442 if (disabled || !app_context_path.has_value()) {
443 s_log->info(
"App context disabled (not configured)");
446 auto path = *app_context_path;
449 if (!path.has_parent_path() || path.parent_path().empty()) {
450 path = data_dir /
"prompts" / path;
453 if (!std::filesystem::exists(path)) {
454 err =
"app_context file not found: " + path.string();
463 body = std::move(result.
body);
464 s_log->info(
"App context loaded from {}", path.string());
488 const std::string& tier_name,
489 const std::filesystem::path& data_dir)
491 std::filesystem::path id_path;
492 if (tier_config.
identity.has_value()) {
493 id_path = tier_config.
identity.value();
495 id_path = data_dir /
"prompts"
496 / (
"identity_" + tier_name +
".md");
499 if (id_path.empty() || !std::filesystem::exists(id_path)) {
504 s_log->warn(
"identity load failed for tier '{}': {}",
508 s_log->info(
"identity loaded for tier '{}' from {}",
509 tier_name, id_path.string());
524 const std::string& tier_name,
525 const std::filesystem::path& data_dir)
528 tier_config, tier_name, data_dir).
body;
545 const std::filesystem::path& data_dir) {
546 std::string constitution, app_ctx;
549 data_dir, constitution);
553 std::string identity_body;
561 if (!constitution.empty()) { prompt += constitution +
"\n\n"; }
562 if (!app_ctx.empty()) { prompt += app_ctx +
"\n\n"; }
563 if (!identity_body.empty()) { prompt += identity_body; }
565 s_log->info(
"system prompt assembled: {} chars "
566 "(constitution={}, app_context={}, identity={})",
567 prompt.size(), !constitution.empty(),
568 !app_ctx.empty(), !identity_body.empty());
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
static std::string trim(const std::string &s)
Trim leading and trailing whitespace from a string.
static void extract_benchmark(ryml::ConstNodeRef root, IdentityFrontmatter &fm)
Extract benchmark config from identity frontmatter.
static void extract_phases(ryml::ConstNodeRef root, IdentityFrontmatter &fm)
Extract phase configs from identity frontmatter.
static void extract_identity_flags(ryml::ConstNodeRef root, IdentityFrontmatter &fm)
Extract the scalar loop/flag fields of an identity.
static void extract_identity_fields(ryml::ConstNodeRef root, IdentityFrontmatter &fm)
Extract all identity frontmatter fields into the struct.
static PromptType prompt_type_from_string(const std::string &type_str, const std::filesystem::path &path, std::string &err)
Map a frontmatter type string to a PromptType.
static void extract_sampler_knobs(ryml::ConstNodeRef root, IdentityFrontmatter &fm)
Extract identity-specific fields from a pre-parsed ryml tree.
static std::string parse_frontmatter(const std::string &content, const std::filesystem::path &path, ryml::Tree &tree, std::string &body)
Parse YAML frontmatter from file content into a ryml tree.
Prompt manager — frontmatter parsing, identity loading, assembly.
ENTROPIC_EXPORT std::string load_app_context(const std::optional< std::filesystem::path > &app_context_path, bool disabled, const std::filesystem::path &data_dir, std::string &body)
Load app_context prompt with tri-state resolution.
ENTROPIC_EXPORT const char * prompt_type_to_string(PromptType type)
Convert PromptType to string.
ENTROPIC_EXPORT ParsedIdentity resolve_tier_identity_full(const entropic::TierConfig &tier_config, const std::string &tier_name, const std::filesystem::path &data_dir)
Resolve full parsed identity (body + frontmatter) for a tier.
ENTROPIC_EXPORT std::string load_constitution(const std::optional< std::filesystem::path > &constitution_path, bool disabled, const std::filesystem::path &data_dir, std::string &body)
Load constitution prompt with tri-state resolution.
ENTROPIC_EXPORT std::string load_identity(const std::filesystem::path &path, ParsedIdentity &identity)
Load an identity file: parse frontmatter + body.
ENTROPIC_EXPORT std::string resolve_tier_identity(const entropic::TierConfig &tier_config, const std::string &tier_name, const std::filesystem::path &data_dir)
Resolve the system prompt body for a named tier.
PromptType
Prompt file type (frontmatter "type" field).
ENTROPIC_EXPORT std::string assemble(const entropic::ParsedConfig &config, const std::filesystem::path &data_dir)
Assemble the full system prompt from config.
ENTROPIC_EXPORT std::string parse_prompt_file(const std::filesystem::path &path, PromptType expected_type, ParsedPrompt &result)
Parse a prompt file: validate frontmatter, return body.
std::unordered_map< std::string, TierConfig > tiers
Tier name → config.
std::string default_tier
Default tier name.
Full parsed configuration.
std::optional< std::filesystem::path > app_context
App context: nullopt = disabled by default.
ModelsConfig models
Tiers + router.
bool app_context_disabled
true if app_context explicitly disabled
std::optional< std::filesystem::path > constitution
Constitution: nullopt = bundled default, disabled = explicit false.
bool constitution_disabled
true if constitution explicitly disabled
Inference parameters for a single identity phase.
int max_output_tokens
Max tokens per generation.
float repeat_penalty
Repetition penalty.
bool enable_thinking
Enable think-block output.
std::optional< std::vector< std::string > > bash_commands
Phase-specific bash commands.
float temperature
Sampling temperature.
Tier-specific model configuration.
std::optional< std::filesystem::path > identity
Identity prompt path (nullopt = bundled)
bool identity_disabled
true if identity explicitly disabled
A single benchmark prompt with quality checks.
std::vector< std::string > checks_yaml
Check defs as YAML strings.
std::string prompt
Prompt text.
Identity frontmatter — full tier identity metadata.
std::vector< std::string > validation_rules
Per-identity constitutional rules (v2.0.6)
std::optional< std::vector< std::string > > allowed_tools
Tool filter.
std::optional< float > top_p
Per-tier top_p (gh#85); nullopt = use param default.
std::optional< float > repeat_penalty
Per-tier repeat_penalty (gh#86); nullopt = use param default.
std::optional< std::string > auto_chain
Auto-chain target tier.
int max_iterations
Per-identity loop iteration cap; -1 = use global (E6)
std::optional< float > min_p
Per-tier min_p (gh#85); nullopt = use param default.
std::vector< std::string > focus
Focus areas (min 1)
std::optional< float > presence_penalty
Per-tier presence_penalty (gh#85); nullopt = use param default.
PromptType type
Always IDENTITY.
bool explicit_completion
Requires explicit completion.
std::optional< bool > enable_thinking
Per-tier thinking mode (gh#86); nullopt = use param default.
std::optional< int > max_output_tokens
Per-tier max output tokens (gh#82); nullopt = use param default.
std::string name
Tier name (e.g., "lead")
bool routable
Visible to router.
int version
Schema version.
std::optional< int > top_k
Per-tier top_k (gh#85); nullopt = use param default.
bool relay_single_delegate
Skip re-synthesis when single delegate returns (v2.0.11)
std::optional< std::vector< std::string > > bash_commands
Allowed bash commands.
std::optional< float > frequency_penalty
Per-tier frequency_penalty (gh#85); nullopt = use param default.
int max_tool_calls_per_turn
Per-identity tool call cap; -1 = use global (E6)
bool interstitial
Interstitial role.
std::optional< std::unordered_map< std::string, PhaseConfig > > phases
Named phases.
std::vector< std::string > examples
Few-shot examples.
std::optional< BenchmarkSpec > benchmark
Benchmark definition.
std::optional< std::string > grammar
Grammar file reference.
std::optional< float > temperature
Per-tier sampling temperature (gh#82); nullopt = use param default.
Parsed identity file: frontmatter + body.
IdentityFrontmatter frontmatter
Full identity metadata.
std::string body
Markdown system prompt body.
Parsed prompt file result: type + version + body.
PromptType type
Prompt type.
std::string body
Markdown body after frontmatter.
int version
Schema version.
std::string read_file(const std::filesystem::path &path)
Read a file into a string.
bool extract_string_list(ryml::ConstNodeRef node, c4::csubstr key, std::vector< std::string > &out)
Extract a vector of strings from a YAML sequence node.
bool extract_string_list_opt(ryml::ConstNodeRef node, c4::csubstr key, std::optional< std::vector< std::string > > &out)
Extract an optional vector of strings.
std::string to_string(c4::csubstr s)
Convert ryml csubstr to std::string.
bool extract(ryml::ConstNodeRef node, c4::csubstr key, std::string &out)
Extract a string value from a YAML node.
ryml extraction helpers for config parsing.