// @harn-entrypoint-category personas.compiler
//
// std/personas/compiler — closed prompt-persona blueprint validation and lowering.
//
// The model-facing blueprint intentionally cannot carry TOML, Harn source,
// capabilities, budgets, model policy, filters, destinations, or authority.
// The CLI materializer owns those package bytes and atomically publishes only a
// successfully validated lowering.
import "std/calendar"
import { typed_output_checkpoint } from "std/checkpoint"
import "std/schema"
import { list_providers } from "std/triggers"
const PERSONA_BLUEPRINT_SCHEMA_VERSION: string = "1"
const __PERSONA_TEMPLATE_IDS: list<string> = ["deterministic-sweeper", "hybrid-classify-then-act", "frontier-judgment-loop"]
pub type PersonaTemplateId = "deterministic-sweeper" | "hybrid-classify-then-act" | "frontier-judgment-loop"
pub type PersonaBlueprintSourceKind = "cron" | "external"
pub type PersonaBlueprintCron = {cron: string, timezone: string}
pub type PersonaBlueprintExternal = {provider: string, event: string}
pub type PersonaBlueprint = {
schema_version: "1",
name: string,
description: string,
goal: string,
template: PersonaTemplateId,
cron?: PersonaBlueprintCron,
external?: PersonaBlueprintExternal,
}
pub type PersonaBlueprintDiagnostic = {code: string, path: string, message: string}
pub type PersonaBlueprintValidationReport = {
valid: bool,
schema_version?: string,
source_kind?: PersonaBlueprintSourceKind,
errors: list<PersonaBlueprintDiagnostic>,
warnings: list<PersonaBlueprintDiagnostic>,
}
pub type PersonaBlueprintTrigger = {
id: string,
kind: string,
provider: string,
events: list<string>,
secrets: dict,
schedule?: string,
timezone?: string,
handler: string,
}
pub type PersonaBlueprintLowering = {
profile: "prompt_compiled_v1",
template: PersonaTemplateId,
persona: {name: string, description: string, goal: string},
policy: {autonomy_tier: "suggest", receipt_policy: "required"},
triggers: list<PersonaBlueprintTrigger>,
}
pub type PersonaBlueprintCompileResult = Result<PersonaBlueprintLowering, PersonaBlueprintValidationReport>
pub type PersonaPromptCompileOptions = {
provider?: string,
model?: string,
max_tokens?: int,
name_override?: string,
}
pub type PersonaPromptCatalogEntry = {
provider: string,
transports: list<string>,
required_secrets: list<string>,
}
pub type PersonaPromptCompileUsage = {
input_tokens: int,
output_tokens: int,
total_tokens: int,
realized_cost_usd: float?,
}
pub type PersonaPromptCheckpointStatus = "not_attempted" | "accepted" | "schema_rejected" | "validator_rejected"
pub type PersonaPromptCheckpointReceipt = {
status: PersonaPromptCheckpointStatus,
attempts: int,
checkpoint_attempts: int,
repaired: bool,
extracted_json: bool,
provider: string,
model: string,
error_category?: string,
}
pub type PersonaPromptCompileReceipt = {
schema_version: "harn.persona.prompt_compile.v1",
ok: bool,
prompt_digest: string,
catalog_digest: string,
catalog: list<PersonaPromptCatalogEntry>,
checkpoint: PersonaPromptCheckpointReceipt,
usage: PersonaPromptCompileUsage,
blueprint?: PersonaBlueprint,
validation?: PersonaBlueprintValidationReport,
lowering?: PersonaBlueprintLowering,
error?: PersonaBlueprintDiagnostic,
}
/**
* persona_blueprint_schema.
*
* Returns the closed model-output schema. A blueprint has exactly one source
* after semantic validation: either `cron` or `external`, never both.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn persona_blueprint_schema() -> dict {
return schema_closed_object(
{
schema_version: schema_literal(PERSONA_BLUEPRINT_SCHEMA_VERSION),
name: schema_string(),
description: schema_string(),
goal: schema_string(),
template: schema_enum(__PERSONA_TEMPLATE_IDS),
cron: schema_field(schema_closed_object({cron: schema_string(), timezone: schema_string()}), false),
external: schema_field(schema_closed_object({provider: schema_string(), event: schema_string()}), false),
},
)
}
fn __persona_blueprint_blank_report(blueprint) -> PersonaBlueprintValidationReport {
return {valid: true, schema_version: blueprint?.schema_version, source_kind: nil, errors: [], warnings: []}
}
fn __persona_blueprint_error(
report: PersonaBlueprintValidationReport,
code: string,
path: string,
message: string,
) -> PersonaBlueprintValidationReport {
return report + {errors: report.errors + [{code: code, path: path, message: message}]}
}
fn __persona_blueprint_finalize(report: PersonaBlueprintValidationReport) -> PersonaBlueprintValidationReport {
return report + {valid: len(report.errors) == 0}
}
fn __persona_blueprint_identifier(value) -> bool {
return type_of(value) == "string" && regex_match("^[A-Za-z_][A-Za-z0-9_]*$", value) != nil
}
fn __persona_blueprint_nonempty(value) -> bool {
return type_of(value) == "string" && trim(value) != ""
}
fn __persona_blueprint_catalog_entry(providers, provider) {
return providers.find({ entry -> entry?.provider == provider })
}
fn __persona_blueprint_record(blueprint) -> PersonaBlueprint {
let record: PersonaBlueprint = {
schema_version: "1",
name: blueprint.name,
description: blueprint.description,
goal: blueprint.goal,
template: blueprint.template,
}
if blueprint.cron != nil {
record = record + {cron: blueprint.cron}
}
if blueprint.external != nil {
record = record + {external: blueprint.external}
}
return record
}
fn __persona_blueprint_cron_fields(cron) -> list<string> {
return regex_split(trim(cron ?? ""), "\\s+").filter({ field -> field != "" }).to_list()
}
fn __persona_blueprint_validate_cron(blueprint, providers, report: PersonaBlueprintValidationReport) -> PersonaBlueprintValidationReport {
let out = report
const source = blueprint.cron
if len(__persona_blueprint_cron_fields(source.cron)) != 5 || !is_valid_cron(source.cron) {
out = __persona_blueprint_error(
out,
"invalid_cron",
"cron.cron",
"cron source must be a valid five-field cron expression",
)
}
try {
parts(0, source.timezone)
} catch (e) {
out = __persona_blueprint_error(
out,
"invalid_timezone",
"cron.timezone",
"cron timezone is not a supported IANA timezone: " + to_string(e),
)
}
const cron_provider = __persona_blueprint_catalog_entry(providers, "cron")
if cron_provider == nil || !contains(cron_provider.kinds, "cron") {
out = __persona_blueprint_error(
out,
"cron_unavailable",
"cron",
"live trigger catalog does not expose the cron transport",
)
}
return out
}
fn __persona_blueprint_validate_external(
blueprint,
providers,
report: PersonaBlueprintValidationReport,
) -> PersonaBlueprintValidationReport {
let out = report
const source = blueprint.external
const provider = __persona_blueprint_catalog_entry(providers, source.provider)
if provider == nil {
return __persona_blueprint_error(
out,
"unknown_provider",
"external.provider",
"provider `" + source.provider + "` is not in the live trigger catalog",
)
}
if len(provider.kinds) != 1 {
out = __persona_blueprint_error(
out,
"ambiguous_provider_transport",
"external.provider",
"provider `" + source.provider + "` must expose exactly one transport kind",
)
}
if !starts_with(source.event, source.provider + ".") {
out = __persona_blueprint_error(
out,
"event_namespace_mismatch",
"external.event",
"event must begin with the selected provider namespace `" + source.provider + ".`",
)
}
return out
}
/**
* persona_blueprint_validate.
*
* Validates a closed `PersonaBlueprint` without performing an LLM call or a
* filesystem mutation. `providers` exists for deterministic tests; production
* callers leave it nil to use `std/triggers::list_providers()`.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn persona_blueprint_validate(blueprint, providers = nil) -> PersonaBlueprintValidationReport {
if type_of(blueprint) != "dict" {
return {
valid: false,
schema_version: nil,
source_kind: nil,
errors: [{code: "not_a_dict", path: "", message: "persona blueprint must be a dict"}],
warnings: [],
}
}
const shape = schema_check(blueprint, persona_blueprint_schema())
let report = __persona_blueprint_blank_report(blueprint)
if is_err(shape) {
const err = unwrap_err(shape)
for entry in err.errors ?? [] {
report = __persona_blueprint_error(report, "shape", entry?.path ?? "", entry?.message ?? to_string(entry))
}
return __persona_blueprint_finalize(report)
}
if !__persona_blueprint_identifier(blueprint.name) {
report = __persona_blueprint_error(
report,
"invalid_name",
"name",
"persona name must be an identifier-like token",
)
}
if !__persona_blueprint_nonempty(blueprint.description) {
report = __persona_blueprint_error(
report,
"empty_description",
"description",
"description must not be blank",
)
}
if !__persona_blueprint_nonempty(blueprint.goal) {
report = __persona_blueprint_error(report, "empty_goal", "goal", "goal must not be blank")
}
const has_cron = blueprint.cron != nil
const has_external = blueprint.external != nil
if has_cron == has_external {
return __persona_blueprint_finalize(
__persona_blueprint_error(
report,
"source_count",
"",
"persona blueprint must select exactly one source: cron or external",
),
)
}
const catalog = providers ?? list_providers()
if has_cron {
report = __persona_blueprint_validate_cron(blueprint, catalog, report) + {source_kind: "cron"}
} else {
report = __persona_blueprint_validate_external(blueprint, catalog, report) + {source_kind: "external"}
}
return __persona_blueprint_finalize(report)
}
fn __persona_blueprint_required_secret_refs(provider) -> dict {
let secrets = {}
for requirement in provider.secret_requirements {
if requirement.required {
// A lowering carries stable identifiers, never secret material. Package
// validation owns the check that each provider-scoped reference is valid.
secrets = secrets + {[requirement.name]: provider.provider + "/" + requirement.name}
}
}
return secrets
}
fn __persona_blueprint_lower_trigger(blueprint, providers) -> PersonaBlueprintTrigger {
const handler = "persona://" + blueprint.name
if blueprint.cron != nil {
return {
id: blueprint.name + "-cron",
kind: "cron",
provider: "cron",
events: ["cron.tick"],
secrets: {},
schedule: blueprint.cron.cron,
timezone: blueprint.cron.timezone,
handler: handler,
}
}
const provider = __persona_blueprint_catalog_entry(providers, blueprint.external.provider)
return {
id: blueprint.name + "-" + provider.kinds[0],
kind: provider.kinds[0],
provider: blueprint.external.provider,
events: [blueprint.external.event],
secrets: __persona_blueprint_required_secret_refs(provider),
schedule: nil,
timezone: nil,
handler: handler,
}
}
/**
* persona_blueprint_compile.
*
* Lowers a valid blueprint to the fixed prompt-compiled safety profile. This
* never emits package bytes; the canonical scaffold transaction materializes
* the resulting template, policy, and trigger plan atomically. Returns
* `Ok(lowering)` on success or `Err(validation)` without side effects.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn persona_blueprint_compile(blueprint, providers = nil) -> PersonaBlueprintCompileResult {
const catalog = providers ?? list_providers()
const validation = persona_blueprint_validate(blueprint, catalog)
if !validation.valid {
return Err(validation)
}
const typed = __persona_blueprint_record(blueprint)
const trigger = __persona_blueprint_lower_trigger(typed, catalog)
return Ok(
{
profile: "prompt_compiled_v1",
template: typed.template,
persona: {name: typed.name, description: typed.description, goal: typed.goal},
policy: {autonomy_tier: "suggest", receipt_policy: "required"},
triggers: [trigger],
},
)
}
fn __persona_prompt_catalog(providers) -> list<PersonaPromptCatalogEntry> {
let catalog: list<PersonaPromptCatalogEntry> = []
for provider in providers {
const transports = provider.kinds.map({ kind -> to_string(kind) }).sort_by({ kind -> kind })
const required_secrets = provider.secret_requirements
.filter({ requirement -> requirement.required })
.map({ requirement -> to_string(requirement.name) })
.sort_by({ name -> name })
catalog = catalog
.push(
{provider: provider.provider, transports: transports, required_secrets: required_secrets},
)
}
return catalog.sort_by({ entry -> entry.provider })
}
fn __persona_prompt_digest(value) -> string {
return "sha256:" + sha256(json_stringify(value))
}
fn __persona_prompt_grounding(user_prompt: string, catalog: list<PersonaPromptCatalogEntry>) -> string {
return "Compile the user's request into exactly one closed PersonaBlueprint JSON object.\n"
+ "Do not emit TOML, Harn source, paths, tools, capabilities, budgets, model policy, filters, destinations, or authority.\n"
+ "Choose one template: deterministic-sweeper for periodic watches/digests; hybrid-classify-then-act for event triage; frontier-judgment-loop only for bounded judgment work.\n"
+ "Choose exactly one source: cron {cron, timezone} or external {provider, event}. External events must begin with '<provider>.'.\n"
+ "Live provider transports: "
+ json_stringify(catalog)
+ "\nExamples:\n"
+ "SDK watch -> {\"schema_version\":\"1\",\"name\":\"sdk_watch\",\"description\":\"Narrates meaningful SDK changes.\",\"goal\":\"Watch the SDK and explain meaningful changes every morning.\",\"template\":\"deterministic-sweeper\",\"cron\":{\"cron\":\"0 9 * * *\",\"timezone\":\"UTC\"}}\n"
+ "Slack triage -> {\"schema_version\":\"1\",\"name\":\"alerts_triage\",\"description\":\"Classifies incoming alerts.\",\"goal\":\"Page, investigate, or ignore each alert.\",\"template\":\"hybrid-classify-then-act\",\"external\":{\"provider\":\"slack\",\"event\":\"slack.message\"}}\n"
+ "Four-hour digest -> {\"schema_version\":\"1\",\"name\":\"reply_digest\",\"description\":\"Summarizes follow-up work.\",\"goal\":\"Surface replies and follow-ups every four hours.\",\"template\":\"deterministic-sweeper\",\"cron\":{\"cron\":\"0 */4 * * *\",\"timezone\":\"UTC\"}}\n"
+ "User request:\n"
+ user_prompt
}
fn __persona_prompt_with_name_override(blueprint, options: PersonaPromptCompileOptions) {
if options.name_override != nil {
return blueprint + {name: options.name_override}
}
return blueprint
}
fn __persona_prompt_usage(checkpoint) -> PersonaPromptCompileUsage {
const raw = checkpoint.usage ?? {}
const input_tokens = to_int(raw?.input_tokens ?? raw?.prompt_tokens) ?? 0
const output_tokens = to_int(raw?.output_tokens ?? raw?.completion_tokens) ?? 0
const total_tokens = to_int(raw?.total_tokens) ?? (input_tokens + output_tokens)
return {
input_tokens: input_tokens,
output_tokens: output_tokens,
total_tokens: total_tokens,
realized_cost_usd: to_float(raw?.cost_usd),
}
}
fn __persona_prompt_checkpoint_receipt(checkpoint) -> PersonaPromptCheckpointReceipt {
const status = if checkpoint.status == "accepted" {
"accepted"
} else if checkpoint.status == "validator_rejected" {
"validator_rejected"
} else {
"schema_rejected"
}
return {
status: status,
attempts: checkpoint.attempts,
checkpoint_attempts: checkpoint.checkpoint_attempts,
repaired: checkpoint.repaired,
extracted_json: checkpoint.extracted_json,
provider: checkpoint.provider,
model: checkpoint.model,
error_category: checkpoint.error_category,
}
}
fn __persona_prompt_not_attempted() -> PersonaPromptCheckpointReceipt {
return {
status: "not_attempted",
attempts: 0,
checkpoint_attempts: 0,
repaired: false,
extracted_json: false,
provider: "",
model: "",
error_category: nil,
}
}
fn __persona_prompt_zero_usage() -> PersonaPromptCompileUsage {
return {input_tokens: 0, output_tokens: 0, total_tokens: 0, realized_cost_usd: nil}
}
fn __persona_prompt_preflight_failure(
prompt_digest: string,
catalog_digest: string,
catalog: list<PersonaPromptCatalogEntry>,
code: string,
path: string,
message: string,
) -> PersonaPromptCompileReceipt {
return {
schema_version: "harn.persona.prompt_compile.v1",
ok: false,
prompt_digest: prompt_digest,
catalog_digest: catalog_digest,
catalog: catalog,
checkpoint: __persona_prompt_not_attempted(),
usage: __persona_prompt_zero_usage(),
blueprint: nil,
validation: nil,
lowering: nil,
error: {code: code, path: path, message: message},
}
}
fn __persona_prompt_validation_error(report: PersonaBlueprintValidationReport) -> PersonaBlueprintDiagnostic {
if len(report.errors) > 0 {
return report.errors[0]
?? {code: "blueprint_invalid", path: "", message: "persona blueprint failed validation"}
}
return {code: "blueprint_invalid", path: "", message: "persona blueprint failed validation"}
}
/**
* persona_compile_prompt.
*
* Compiles one natural-language request into the closed persona blueprint and
* deterministic prompt_compiled_v1 lowering. The checkpoint is deliberately
* single-shot: schema and validator retries are zero and repair is disabled.
* The returned receipt contains only prompt/catalog digests, compact catalog
* facts, normalized usage/cost, the validated blueprint, and its lowering.
*
* @effects: [llm]
* @errors: []
* @api_stability: experimental
*/
pub fn persona_compile_prompt(
prompt: string,
options: PersonaPromptCompileOptions? = nil,
providers = nil,
) -> PersonaPromptCompileReceipt {
const opts: PersonaPromptCompileOptions = options ?? {}
const live_providers = providers ?? list_providers()
const catalog = __persona_prompt_catalog(live_providers)
const prompt_digest = __persona_prompt_digest(trim(prompt))
const catalog_digest = __persona_prompt_digest(catalog)
const max_tokens = opts.max_tokens ?? 512
if trim(prompt) == "" {
return __persona_prompt_preflight_failure(
prompt_digest,
catalog_digest,
catalog,
"blank_prompt",
"prompt",
"persona prompt must not be blank",
)
}
if max_tokens < 1 || max_tokens > 1200 {
return __persona_prompt_preflight_failure(
prompt_digest,
catalog_digest,
catalog,
"max_tokens_out_of_range",
"options.max_tokens",
"persona prompt max_tokens must be between 1 and 1200",
)
}
const checkpoint = typed_output_checkpoint(
"personas.compile_prompt",
__persona_prompt_grounding(trim(prompt), catalog),
persona_blueprint_schema(),
{
provider: opts.provider,
model: opts.model,
max_tokens: max_tokens,
schema_retries: 0,
validator_retries: 0,
repair: {enabled: false},
},
fn(candidate) {
const normalized = __persona_prompt_with_name_override(candidate, opts)
const validation = persona_blueprint_validate(normalized, live_providers)
return {
ok: validation.valid,
errors: validation.errors.map({ diagnostic -> diagnostic.path + ": " + diagnostic.message }),
}
},
)
const checkpoint_receipt = __persona_prompt_checkpoint_receipt(checkpoint)
const usage = __persona_prompt_usage(checkpoint)
if !checkpoint.ok {
if checkpoint.status == "validator_rejected" {
const blueprint = __persona_blueprint_record(__persona_prompt_with_name_override(checkpoint.data, opts))
const validation = persona_blueprint_validate(blueprint, live_providers)
return {
schema_version: "harn.persona.prompt_compile.v1",
ok: false,
prompt_digest: prompt_digest,
catalog_digest: catalog_digest,
catalog: catalog,
checkpoint: checkpoint_receipt,
usage: usage,
blueprint: blueprint,
validation: validation,
lowering: nil,
error: __persona_prompt_validation_error(validation),
}
}
return {
schema_version: "harn.persona.prompt_compile.v1",
ok: false,
prompt_digest: prompt_digest,
catalog_digest: catalog_digest,
catalog: catalog,
checkpoint: checkpoint_receipt,
usage: usage,
blueprint: nil,
validation: nil,
lowering: nil,
error: {code: checkpoint.error_category ?? "schema_rejected", path: "", message: checkpoint.error},
}
}
const blueprint = __persona_blueprint_record(__persona_prompt_with_name_override(checkpoint.data, opts))
const validation = persona_blueprint_validate(blueprint, live_providers)
const compiled = persona_blueprint_compile(blueprint, live_providers)
if is_err(compiled) {
const failed = unwrap_err(compiled)
return {
schema_version: "harn.persona.prompt_compile.v1",
ok: false,
prompt_digest: prompt_digest,
catalog_digest: catalog_digest,
catalog: catalog,
checkpoint: checkpoint_receipt,
usage: usage,
blueprint: blueprint,
validation: failed,
lowering: nil,
error: __persona_prompt_validation_error(failed),
}
}
return {
schema_version: "harn.persona.prompt_compile.v1",
ok: true,
prompt_digest: prompt_digest,
catalog_digest: catalog_digest,
catalog: catalog,
checkpoint: checkpoint_receipt,
usage: usage,
blueprint: blueprint,
validation: validation,
lowering: unwrap(compiled),
error: nil,
}
}