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));
259 extract(root,
"routable", fm.
routable);
276 fm.
type = PromptType::IDENTITY;
277 extract(root,
"version", fm.
version);
278 extract(root,
"name", fm.
name);
279 extract_string_list(root,
"focus", fm.
focus);
280 extract_string_list(root,
"examples", fm.
examples);
282 std::string grammar_str;
283 if (extract(root,
"grammar", grammar_str)) {
286 std::string auto_chain_str;
287 if (extract(root,
"auto_chain", auto_chain_str)) {
291 extract_string_list_opt(root,
"allowed_tools", fm.
allowed_tools);
292 extract_string_list_opt(root,
"bash_commands", fm.
bash_commands);
309 const std::filesystem::path& path,
314 auto content = read_file(path);
315 if (content.empty()) {
316 err =
"cannot read identity file: " + path.string();
324 ryml::ConstNodeRef root;
326 root = tree.rootref();
329 std::string type_str;
330 if (!extract(root,
"type", type_str) || type_str !=
"identity") {
331 err =
"prompt file " + path.string()
332 +
" is not an identity file (type='"
341 err =
"identity " + path.string()
342 +
": focus must have at least one entry";
344 err =
"identity " + path.string()
345 +
": name must not be empty";
363 const std::optional<std::filesystem::path>& constitution_path,
365 const std::filesystem::path& data_dir,
371 s_log->info(
"Constitution disabled by config");
374 std::filesystem::path path = constitution_path.has_value()
376 : data_dir /
"prompts" /
"constitution.md";
378 if (!std::filesystem::exists(path)) {
379 err =
"constitution file not found: " + path.string();
388 body = std::move(result.
body);
389 s_log->info(
"Constitution loaded from {}", path.string());
407 const std::optional<std::filesystem::path>& app_context_path,
409 const std::filesystem::path& data_dir,
414 if (disabled || !app_context_path.has_value()) {
415 s_log->info(
"App context disabled (not configured)");
418 auto path = *app_context_path;
421 if (!path.has_parent_path() || path.parent_path().empty()) {
422 path = data_dir /
"prompts" / path;
425 if (!std::filesystem::exists(path)) {
426 err =
"app_context file not found: " + path.string();
435 body = std::move(result.
body);
436 s_log->info(
"App context loaded from {}", path.string());
460 const std::string& tier_name,
461 const std::filesystem::path& data_dir)
463 std::filesystem::path id_path;
464 if (tier_config.
identity.has_value()) {
465 id_path = tier_config.
identity.value();
467 id_path = data_dir /
"prompts"
468 / (
"identity_" + tier_name +
".md");
471 if (id_path.empty() || !std::filesystem::exists(id_path)) {
476 s_log->warn(
"identity load failed for tier '{}': {}",
480 s_log->info(
"identity loaded for tier '{}' from {}",
481 tier_name, id_path.string());
496 const std::string& tier_name,
497 const std::filesystem::path& data_dir)
500 tier_config, tier_name, data_dir).
body;
517 const std::filesystem::path& data_dir) {
518 std::string constitution, app_ctx;
521 data_dir, constitution);
525 std::string identity_body;
533 if (!constitution.empty()) { prompt += constitution +
"\n\n"; }
534 if (!app_ctx.empty()) { prompt += app_ctx +
"\n\n"; }
535 if (!identity_body.empty()) { prompt += identity_body; }
537 s_log->info(
"system prompt assembled: {} chars "
538 "(constitution={}, app_context={}, identity={})",
539 prompt.size(), !constitution.empty(),
540 !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 identity-specific fields from a pre-parsed ryml tree.
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 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< std::string > auto_chain
Auto-chain target tier.
int max_output_tokens
Default max output tokens.
int max_iterations
Per-identity loop iteration cap; -1 = use global (E6)
std::vector< std::string > focus
Focus areas (min 1)
PromptType type
Always IDENTITY.
bool explicit_completion
Requires explicit completion.
std::string name
Tier name (e.g., "lead")
bool routable
Visible to router.
int version
Schema version.
bool relay_single_delegate
Skip re-synthesis when single delegate returns (v2.0.11)
bool enable_thinking
Default thinking mode.
float repeat_penalty
Default repetition penalty.
std::optional< std::vector< std::string > > bash_commands
Allowed bash commands.
int max_tool_calls_per_turn
Per-identity tool call cap; -1 = use global (E6)
bool interstitial
Interstitial role.
float temperature
Default temperature.
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.
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.