// @harn-entrypoint-category personas.prompt_compiler
//
// std/personas/prompt_compiler — natural-language persona blueprint compilation.
//
// This module owns the model-facing candidate and receipt. Deterministic
// blueprint validation and lowering stay in std/personas/compiler so replay and
// materialization never load the LLM checkpoint or its schema.
import { typed_output_checkpoint } from "std/checkpoint"
import {
PersonaBlueprint,
PersonaBlueprintDiagnostic,
PersonaBlueprintLowering,
PersonaBlueprintValidationReport,
PersonaTemplateId,
persona_blueprint_compile,
persona_blueprint_validate,
} from "std/personas/compiler"
import "std/schema"
import { list_providers } from "std/triggers"
type PersonaPromptBlueprintSource = {kind: "cron", cron: string, timezone: string} | {kind: "external", provider: string, event: string}
type PersonaPromptBlueprintCandidate = {
schema_version: "1",
name: string,
description: string,
goal: string,
template: PersonaTemplateId,
source: PersonaPromptBlueprintSource,
}
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,
}
fn __persona_prompt_blueprint_schema() -> dict {
return schema_closed_object(
{
schema_version: schema_literal("1"),
name: schema_string(),
description: schema_string(),
goal: schema_string(),
template: schema_enum(["deterministic-sweeper", "hybrid-classify-then-act", "frontier-judgment-loop"]),
source: schema_union(
[
schema_closed_object(
{kind: schema_literal("cron"), cron: schema_string(), timezone: schema_string()},
),
schema_closed_object(
{kind: schema_literal("external"), provider: schema_string(), event: schema_string()},
),
],
),
},
)
}
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"
+ "Set source to exactly one closed variant: {kind: 'cron', cron, timezone} or {kind: '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\",\"source\":{\"kind\":\"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\",\"source\":{\"kind\":\"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\",\"source\":{\"kind\":\"cron\",\"cron\":\"0 */4 * * *\",\"timezone\":\"UTC\"}}\n"
+ "User request:\n"
+ user_prompt
}
fn __persona_prompt_candidate_blueprint(
candidate: PersonaPromptBlueprintCandidate,
options: PersonaPromptCompileOptions,
) -> PersonaBlueprint {
let blueprint: PersonaBlueprint = {
schema_version: "1",
name: candidate.name,
description: candidate.description,
goal: candidate.goal,
template: candidate.template,
}
if candidate.source.kind == "cron" {
blueprint = blueprint
+ {cron: {cron: candidate.source.cron, timezone: candidate.source.timezone}}
} else {
blueprint = blueprint
+ {external: {provider: candidate.source.provider, event: candidate.source.event}}
}
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_prompt_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_candidate_blueprint(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_prompt_candidate_blueprint(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_prompt_candidate_blueprint(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,
}
}