Soul s1 fixes: pin the lowerer reach, bound lowering text, pin the build refusal and the I/O discard
RunRecord key 12 LowererReach set by a required --lowerer-reach flag and shown in round and cycle lines. PROMPT_SOURCE_DIGEST covers packet.rs. Lowering text is refused when blank, over 4096 bytes or holding a control character. Unanswered lowering digests are listed once. Tests for the Prompt::lower build refusal and the packets.cc discard after a port error. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
74ebafbc65
commit
b569028f48
10 changed files with 355 additions and 46 deletions
|
|
@ -79,7 +79,7 @@ Answer with one JSON object and nothing else: {\"text\": \"...\"}. No code fence
|
|||
/// # let p = |n: &str| dir.path().join(n);
|
||||
/// # for n in ["c.cc", "i.cc", "e.cc"] { std::fs::write(p(n), [0x90]).unwrap(); }
|
||||
/// # let paths = weksa::run::RunPaths { corpus: &p("c.cc"), inventory: &p("i.cc"), entities: &p("e.cc") };
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e").unwrap();
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e", weksa::run::LowererReach::ToolLessRelay).unwrap();
|
||||
/// let t = run.train().unwrap();
|
||||
/// assert!(t.is_empty());
|
||||
/// ```
|
||||
|
|
@ -93,7 +93,7 @@ Answer with one JSON object and nothing else: {\"text\": \"...\"}. No code fence
|
|||
/// # let p = |n: &str| dir.path().join(n);
|
||||
/// # for n in ["c.cc", "i.cc", "e.cc"] { std::fs::write(p(n), [0x90]).unwrap(); }
|
||||
/// # let paths = weksa::run::RunPaths { corpus: &p("c.cc"), inventory: &p("i.cc"), entities: &p("e.cc") };
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e").unwrap();
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e", weksa::run::LowererReach::ToolLessRelay).unwrap();
|
||||
/// let t = run.train().unwrap();
|
||||
/// let _u = t.clone();
|
||||
/// ```
|
||||
|
|
@ -104,7 +104,7 @@ Answer with one JSON object and nothing else: {\"text\": \"...\"}. No code fence
|
|||
/// # let p = |n: &str| dir.path().join(n);
|
||||
/// # for n in ["c.cc", "i.cc", "e.cc"] { std::fs::write(p(n), [0x90]).unwrap(); }
|
||||
/// # let paths = weksa::run::RunPaths { corpus: &p("c.cc"), inventory: &p("i.cc"), entities: &p("e.cc") };
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e").unwrap();
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e", weksa::run::LowererReach::ToolLessRelay).unwrap();
|
||||
/// let t = run.train().unwrap();
|
||||
/// s(&t);
|
||||
/// ```
|
||||
|
|
@ -310,7 +310,7 @@ impl EditFeedback {
|
|||
/// # let p = |n: &str| dir.path().join(n);
|
||||
/// # for n in ["c.cc", "i.cc", "e.cc"] { std::fs::write(p(n), [0x90]).unwrap(); }
|
||||
/// # let paths = weksa::run::RunPaths { corpus: &p("c.cc"), inventory: &p("i.cc"), entities: &p("e.cc") };
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e").unwrap();
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e", weksa::run::LowererReach::ToolLessRelay).unwrap();
|
||||
/// let t = run.train().unwrap();
|
||||
/// let inv = run.inventory().inventory().unwrap();
|
||||
/// let feedback = weksa::context::EditFeedback::none();
|
||||
|
|
@ -328,7 +328,7 @@ impl EditFeedback {
|
|||
/// # let p = |n: &str| dir.path().join(n);
|
||||
/// # for n in ["c.cc", "i.cc", "e.cc"] { std::fs::write(p(n), [0x90]).unwrap(); }
|
||||
/// # let paths = weksa::run::RunPaths { corpus: &p("c.cc"), inventory: &p("i.cc"), entities: &p("e.cc") };
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e").unwrap();
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e", weksa::run::LowererReach::ToolLessRelay).unwrap();
|
||||
/// let t = run.train().unwrap();
|
||||
/// let inv = run.inventory().inventory().unwrap();
|
||||
/// let feedback = weksa::context::EditFeedback::none();
|
||||
|
|
@ -341,7 +341,7 @@ impl EditFeedback {
|
|||
/// # let p = |n: &str| dir.path().join(n);
|
||||
/// # for n in ["c.cc", "i.cc", "e.cc"] { std::fs::write(p(n), [0x90]).unwrap(); }
|
||||
/// # let paths = weksa::run::RunPaths { corpus: &p("c.cc"), inventory: &p("i.cc"), entities: &p("e.cc") };
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e").unwrap();
|
||||
/// # let run = weksa::run::RunStores::create(&p("run"), "t", paths, "m", "e", weksa::run::LowererReach::ToolLessRelay).unwrap();
|
||||
/// let t = run.train().unwrap();
|
||||
/// let inv = run.inventory().inventory().unwrap();
|
||||
/// let feedback = weksa::context::EditFeedback::none();
|
||||
|
|
@ -498,8 +498,11 @@ impl Prompt {
|
|||
/// [`LOWER_INSTRUCTIONS`], the meaning of `input` (no kind, constraints,
|
||||
/// scene, speaker or group), the en-US forms of every entity its referents
|
||||
/// cite, and the entries of `inventory` live at the view's revision. `item`
|
||||
/// goes only to [`Prompt::item_keys`], never into the text. A cited entity
|
||||
/// the port lacks, or one with no en-US form, is an error and no prompt.
|
||||
/// goes only to [`Prompt::item_keys`], never into the text. The view's
|
||||
/// speaker and group fields are never read; a speaker's entity id appears
|
||||
/// only when the encoder cites that entity on a referent, which is naming a
|
||||
/// cited entity like any other. A cited entity the port lacks, or one with
|
||||
/// no en-US form, is an error and no prompt.
|
||||
pub fn lower(
|
||||
input: &LoweringInput,
|
||||
item: &CorpusItemId,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ impl fmt::Display for DraftError {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"not a packet draft at line {} column {}",
|
||||
"not a draft at line {} column {}",
|
||||
self.line, self.column
|
||||
)
|
||||
}
|
||||
|
|
@ -207,10 +207,26 @@ pub struct LoweringDraft {
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
/// The most bytes a lowering may hold: well over any one utterance, far under
|
||||
/// a megabyte the embedder would be asked to read.
|
||||
pub const MAX_LOWERING_BYTES: usize = 4096;
|
||||
|
||||
impl LoweringDraft {
|
||||
pub fn parse(answer: &str) -> Result<Self, DraftError> {
|
||||
from_json(answer)
|
||||
}
|
||||
|
||||
/// Whether the text can be scored as an utterance: something after trimming,
|
||||
/// at most [`MAX_LOWERING_BYTES`], and no control character other than a
|
||||
/// line break or tab. Nothing is judged about the words.
|
||||
pub fn usable(&self) -> bool {
|
||||
!self.text.trim().is_empty()
|
||||
&& self.text.len() <= MAX_LOWERING_BYTES
|
||||
&& !self
|
||||
.text
|
||||
.chars()
|
||||
.any(|c| c.is_control() && !matches!(c, '\n' | '\t'))
|
||||
}
|
||||
}
|
||||
|
||||
/// `encoder:<hex16>`, a digest of the model id and the prompt digest.
|
||||
|
|
@ -347,7 +363,7 @@ mod tests {
|
|||
fn a_draft_error_is_a_position_and_echoes_no_token() {
|
||||
let err = PacketDraft::parse("{\n \"confidence\": CANARYTOKEN").unwrap_err();
|
||||
assert_eq!((err.line, err.column), (2, 17));
|
||||
assert_eq!(err.to_string(), "not a packet draft at line 2 column 17");
|
||||
assert_eq!(err.to_string(), "not a draft at line 2 column 17");
|
||||
assert_eq!(format!("{err:?}"), err.to_string());
|
||||
let err = PacketDraft::parse(r#"{"confidence":500,"CANARYKEY":1}"#).unwrap_err();
|
||||
for text in [err.to_string(), format!("{err:?}")] {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use weksa::corpus::{CorpusItem, ModelFamily, NameSpan, Source, Turn};
|
|||
use weksa::entity::{Entity, EntityOrigin, LanguageForms, Pronunciation};
|
||||
use weksa::inventory::{proof_revision, seed};
|
||||
use weksa::packet::proof_packet;
|
||||
use weksa::run::{CycleOutcome, RoundOutcome, RunPaths, RunStores, cycle, round};
|
||||
use weksa::run::{CycleOutcome, LowererReach, RoundOutcome, RunPaths, RunStores, cycle, round};
|
||||
use weksa::score::ScoreInput;
|
||||
use weksa::{
|
||||
AnswerFiles, CorpusStore, EntityId, EntityStore, InventoryStore, OllamaEmbedder, PacketStore,
|
||||
|
|
@ -88,6 +88,10 @@ enum LoopCommand {
|
|||
answerer: String,
|
||||
#[arg(long, default_value = "qwen3-embedding:0.6b")]
|
||||
embed_model: String,
|
||||
/// How lowering prompts will be answered: briefed-subagent (smoke
|
||||
/// rounds only) or tool-less-relay. Pinned in the run's record.
|
||||
#[arg(long)]
|
||||
lowerer_reach: LowererReach,
|
||||
},
|
||||
/// Run one round of the run at ROOT. The stores are read only from the
|
||||
/// run's record: the global --corpus, --inventory and --entities are not
|
||||
|
|
@ -734,6 +738,7 @@ fn main() -> Result<()> {
|
|||
label,
|
||||
answerer,
|
||||
embed_model,
|
||||
lowerer_reach,
|
||||
}) => {
|
||||
let run = RunStores::create(
|
||||
&root,
|
||||
|
|
@ -745,6 +750,7 @@ fn main() -> Result<()> {
|
|||
},
|
||||
&answerer,
|
||||
&embed_model,
|
||||
lowerer_reach,
|
||||
)?;
|
||||
println!("opened run:{}", run.record().label);
|
||||
}
|
||||
|
|
@ -765,7 +771,7 @@ fn main() -> Result<()> {
|
|||
println!("unanswered {} of {asked}", prompts.len());
|
||||
std::process::exit(3);
|
||||
}
|
||||
RoundOutcome::Recorded(r) => println!("{}", r.summary_line()),
|
||||
RoundOutcome::Recorded(r) => println!("{}", run.labelled(&r.summary_line())),
|
||||
}
|
||||
}
|
||||
Command::Loop(LoopCommand::Cycle { root, ollama_url }) => {
|
||||
|
|
@ -784,7 +790,9 @@ fn main() -> Result<()> {
|
|||
println!("unanswered {} prompts", prompts.len());
|
||||
std::process::exit(3);
|
||||
}
|
||||
CycleOutcome::Decided(decision) => println!("{}", decision.summary_line()),
|
||||
CycleOutcome::Decided(decision) => {
|
||||
println!("{}", run.labelled(&decision.summary_line()))
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Schema { out } => {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ use crate::validate::LoweringInput;
|
|||
/// The digest of the prompt builder's source: `context.rs`, then `split.rs`,
|
||||
/// whose scheme and digest function decide which lines the prompt holds as
|
||||
/// Train and name every prompt, then `lower.rs`, whose `TARGET_LANGUAGE` and
|
||||
/// `EntityForms` port the lowering prompt reads. Each file enters the digest
|
||||
/// `EntityForms` port the lowering prompt reads, then `packet.rs`, whose serde
|
||||
/// shapes the lowering prompt prints as the packet. Each file enters the digest
|
||||
/// behind its length, so the parts cannot be re-cut into other parts with the
|
||||
/// same digest. Any edit to any of them, comments included, ends a run; code
|
||||
/// outside those files is not covered.
|
||||
|
|
@ -54,6 +55,7 @@ pub const PROMPT_SOURCE_DIGEST: u64 = fnv1a64_of(&[
|
|||
include_bytes!("context.rs"),
|
||||
include_bytes!("split.rs"),
|
||||
include_bytes!("lower.rs"),
|
||||
include_bytes!("packet.rs"),
|
||||
]);
|
||||
|
||||
/// The least composite gain an edit must show over item noise to be kept, and
|
||||
|
|
@ -99,6 +101,43 @@ pub struct RunRecord {
|
|||
/// 16 hex of the bytes `create` copied to `<root>/inventory.cc`.
|
||||
#[cultcache(key = 11)]
|
||||
pub inventory_digest: String,
|
||||
/// How the run's lowering prompts are answered, set once at open.
|
||||
#[cultcache(key = 12)]
|
||||
pub lowerer_reach: LowererReach,
|
||||
}
|
||||
|
||||
/// How a run's lowering prompts reach the model that answers them (rulings
|
||||
/// `lowerer-call-reach-toolless-relay`, `dao-round-seed-inventory`). The
|
||||
/// record says which, so a round lowered by a briefed subagent cannot pass as
|
||||
/// a tool-less one.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum LowererReach {
|
||||
/// A subagent briefed to read one prompt file: acceptable for smoke rounds
|
||||
/// only.
|
||||
BriefedSubagent,
|
||||
/// The prompt passed inline to an agent with no file tools.
|
||||
ToolLessRelay,
|
||||
}
|
||||
|
||||
impl LowererReach {
|
||||
/// The spelling `weksa loop open --lowerer-reach` takes and lines print.
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
LowererReach::BriefedSubagent => "briefed-subagent",
|
||||
LowererReach::ToolLessRelay => "tool-less-relay",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for LowererReach {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, String> {
|
||||
[LowererReach::BriefedSubagent, LowererReach::ToolLessRelay]
|
||||
.into_iter()
|
||||
.find(|r| r.label() == s)
|
||||
.ok_or_else(|| "expected briefed-subagent or tool-less-relay".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a round refused: the first stage that did.
|
||||
|
|
@ -107,7 +146,8 @@ pub enum Stage {
|
|||
Parse,
|
||||
Admit,
|
||||
/// The lowering prompt could not be built, or its answer was not UTF-8, not
|
||||
/// a `LoweringDraft`, or empty text.
|
||||
/// a `LoweringDraft`, or text that is blank, over `MAX_LOWERING_BYTES` or
|
||||
/// holds a control character.
|
||||
Lower,
|
||||
}
|
||||
|
||||
|
|
@ -355,7 +395,7 @@ impl fmt::Display for RunError {
|
|||
RunError::NoRun => f.write_str("the root holds no run"),
|
||||
RunError::Malformed(what) => write!(f, "malformed {what}"),
|
||||
RunError::Drift(DriftOf::Prompts) => f.write_str(
|
||||
"drift in Prompts (the prompt source digest covers context.rs, split.rs and lower.rs)",
|
||||
"drift in Prompts (the prompt source digest covers context.rs, split.rs, lower.rs and packet.rs)",
|
||||
),
|
||||
RunError::Drift(of) => write!(f, "drift in {of:?}"),
|
||||
RunError::NoInventory => f.write_str("the inventory holds no revision"),
|
||||
|
|
@ -479,8 +519,9 @@ impl RunStores {
|
|||
paths: RunPaths<'_>,
|
||||
model: &str,
|
||||
embedder: &str,
|
||||
reach: LowererReach,
|
||||
) -> Result<Self, RunError> {
|
||||
Self::create_between(root, label, paths, model, embedder, || {})
|
||||
Self::create_between(root, label, paths, model, embedder, reach, || {})
|
||||
}
|
||||
|
||||
/// `create` with `between` run after the digests are taken and before the
|
||||
|
|
@ -492,6 +533,7 @@ impl RunStores {
|
|||
paths: RunPaths<'_>,
|
||||
model: &str,
|
||||
embedder: &str,
|
||||
reach: LowererReach,
|
||||
between: impl FnOnce(),
|
||||
) -> Result<Self, RunError> {
|
||||
if !is_slug(label) {
|
||||
|
|
@ -519,6 +561,7 @@ impl RunStores {
|
|||
embedder: embedder.to_string(),
|
||||
chrf_version: CHRF_VERSION.to_string(),
|
||||
inventory_digest: String::new(),
|
||||
lowerer_reach: reach,
|
||||
};
|
||||
// The run works on its own copy; the caller's store is never opened again.
|
||||
let head = root.join(INVENTORY_FILE);
|
||||
|
|
@ -550,7 +593,7 @@ impl RunStores {
|
|||
}
|
||||
|
||||
/// Reopens the run at `root` from its record, refusing when a pinned store
|
||||
/// or the prompt or lowerer source no longer matches.
|
||||
/// or the prompt source (`PROMPT_SOURCE_DIGEST`) no longer matches.
|
||||
pub fn open(root: &Path) -> Result<Self, RunError> {
|
||||
Self::open_between(root, || {})
|
||||
}
|
||||
|
|
@ -593,6 +636,18 @@ impl RunStores {
|
|||
&self.record
|
||||
}
|
||||
|
||||
/// `line` (a round or cycle summary) with how this run's lowerings were
|
||||
/// answered; a briefed-subagent run says its numbers are smoke-round only.
|
||||
pub fn labelled(&self, line: &str) -> String {
|
||||
let reach = self.record.lowerer_reach;
|
||||
match reach {
|
||||
LowererReach::BriefedSubagent => {
|
||||
format!("{line} lowering={} (smoke-round only)", reach.label())
|
||||
}
|
||||
LowererReach::ToolLessRelay => format!("{line} lowering={}", reach.label()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inventory(&self) -> &InventoryStore {
|
||||
&self.inventory
|
||||
}
|
||||
|
|
@ -849,7 +904,7 @@ fn score_round<M: ModelPort, E: Embedder>(
|
|||
});
|
||||
fates[*at] = match model.answer(&prompt) {
|
||||
Ok(answer) => match LoweringDraft::parse(&answer) {
|
||||
Ok(draft) if !draft.text.trim().is_empty() => {
|
||||
Ok(draft) if draft.usable() => {
|
||||
Fate::Lowered(draft.text, packet_key.clone())
|
||||
}
|
||||
_ => Fate::Refused(Stage::Lower, Some(packet_key.clone())),
|
||||
|
|
@ -858,7 +913,10 @@ fn score_round<M: ModelPort, E: Embedder>(
|
|||
Fate::Refused(Stage::Lower, Some(packet_key.clone()))
|
||||
}
|
||||
Err(PortError::Unanswered) => {
|
||||
unanswered.push(prompt.digest());
|
||||
// Two views with one meaning share one prompt: name it once.
|
||||
if !unanswered.contains(&prompt.digest()) {
|
||||
unanswered.push(prompt.digest());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Err(PortError::Io(kind)) => {
|
||||
|
|
@ -1633,6 +1691,7 @@ mod tests {
|
|||
embedder: String::new(),
|
||||
chrf_version: String::new(),
|
||||
inventory_digest: digest.to_string(),
|
||||
lowerer_reach: LowererReach::ToolLessRelay,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1831,7 +1890,7 @@ mod tests {
|
|||
entities: &entities,
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", crate::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
.unwrap();
|
||||
let mut model = fake();
|
||||
|
|
|
|||
|
|
@ -1344,6 +1344,8 @@ fn loop_open_and_unanswered_round() {
|
|||
"smoke",
|
||||
"--answerer",
|
||||
"fake-model",
|
||||
"--lowerer-reach",
|
||||
"tool-less-relay",
|
||||
],
|
||||
)
|
||||
};
|
||||
|
|
@ -1393,7 +1395,7 @@ fn loop_open_and_unanswered_round() {
|
|||
assert!(second.status.success(), "{}", out(&second));
|
||||
assert_eq!(
|
||||
out(&second),
|
||||
"round 1 rev 3 train n=1 scored=0 chrf=0.00 meaning=0.0000 refused=1 validation n=2 scored=0 chrf=0.00 meaning=0.0000 refused parse=2 admit=0 lower=0\n"
|
||||
"round 1 rev 3 train n=1 scored=0 chrf=0.00 meaning=0.0000 refused=1 validation n=2 scored=0 chrf=0.00 meaning=0.0000 refused parse=2 admit=0 lower=0 lowering=tool-less-relay\n"
|
||||
);
|
||||
|
||||
// The global store flags are not read by loop round: a drifted store is refused.
|
||||
|
|
@ -1420,6 +1422,8 @@ fn open_dao_run(d: &std::path::Path) {
|
|||
"smoke",
|
||||
"--answerer",
|
||||
"fake-model",
|
||||
"--lowerer-reach",
|
||||
"tool-less-relay",
|
||||
],
|
||||
);
|
||||
assert!(o.status.success(), "{}", out(&o));
|
||||
|
|
@ -1481,7 +1485,7 @@ fn loop_cycle_asks_then_decides() {
|
|||
assert!(done.status.success(), "{}", out(&done));
|
||||
assert_eq!(
|
||||
out(&done),
|
||||
"cycle 1 rev 3->none base=0.0000 candidate=none floor=0.0000 threshold=0.0050 rejected edit-parse\n"
|
||||
"cycle 1 rev 3->none base=0.0000 candidate=none floor=0.0000 threshold=0.0050 rejected edit-parse lowering=tool-less-relay\n"
|
||||
);
|
||||
|
||||
// The next cycle asks afresh in its own directory.
|
||||
|
|
@ -1520,6 +1524,8 @@ fn loop_errors_echo_no_input() {
|
|||
"CANARYLABEL",
|
||||
"--answerer",
|
||||
"m",
|
||||
"--lowerer-reach",
|
||||
"tool-less-relay",
|
||||
],
|
||||
vec![
|
||||
"loop",
|
||||
|
|
@ -1529,6 +1535,8 @@ fn loop_errors_echo_no_input() {
|
|||
"ok",
|
||||
"--answerer",
|
||||
"CANARY\n",
|
||||
"--lowerer-reach",
|
||||
"tool-less-relay",
|
||||
],
|
||||
] {
|
||||
let o = weksa(d.path(), &args);
|
||||
|
|
@ -1548,9 +1556,39 @@ fn loop_errors_echo_no_input() {
|
|||
"ok",
|
||||
"--answerer",
|
||||
"m",
|
||||
"--lowerer-reach",
|
||||
"tool-less-relay",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(!o.status.success());
|
||||
assert!(!out(&o).to_uppercase().contains("CANARY"), "{}", out(&o));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loop_open_requires_the_lowerer_reach_and_pins_it() {
|
||||
let d = tempfile::tempdir().unwrap();
|
||||
dao_stores(d.path());
|
||||
let base = ["loop", "open", "canaryroot", "--label", "smoke", "--answerer", "m"];
|
||||
// No default: the record says how lowerings are answered, or there is no run.
|
||||
let o = weksa(d.path(), &base);
|
||||
assert!(!o.status.success());
|
||||
assert!(out(&o).contains("--lowerer-reach"), "{}", out(&o));
|
||||
// A value outside the two names is refused without being echoed.
|
||||
let mut args = base.to_vec();
|
||||
args.extend(["--lowerer-reach", "CANARYREACH"]);
|
||||
let o = weksa(d.path(), &args);
|
||||
assert!(!o.status.success());
|
||||
assert!(!out(&o).to_uppercase().contains("CANARY"), "{}", out(&o));
|
||||
assert!(!d.path().join("canaryroot/run.cc").exists());
|
||||
// Either name opens, and the record keeps it.
|
||||
let mut args = base.to_vec();
|
||||
args.extend(["--lowerer-reach", "briefed-subagent"]);
|
||||
let o = weksa(d.path(), &args);
|
||||
assert!(o.status.success(), "{}", out(&o));
|
||||
let run = weksa::run::RunStores::open(&d.path().join("canaryroot")).unwrap();
|
||||
assert_eq!(
|
||||
run.record().lowerer_reach,
|
||||
weksa::run::LowererReach::BriefedSubagent
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ pub fn run_over(dir: &std::path::Path) -> weksa::run::RunStores {
|
|||
entities: &dir.join("entities.cc"),
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ fn a_run_pins_its_stores_and_refuses_drift() {
|
|||
entities: &e,
|
||||
},
|
||||
model,
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ fn a_run_works_on_its_own_copy_of_the_inventory() {
|
|||
entities: &e,
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ fn create_refuses_a_root_that_holds_an_inventory_and_never_truncates_it() {
|
|||
entities: &dao.entities,
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ fn a_create_that_fails_leaves_no_inventory_and_the_retry_succeeds() {
|
|||
entities,
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
};
|
||||
// A directory opens for reading and fails mid-copy, after the destination exists.
|
||||
|
|
@ -381,14 +381,14 @@ fn a_store_changed_between_its_digest_and_its_open_is_drift() {
|
|||
entities: &e,
|
||||
};
|
||||
assert_eq!(
|
||||
RunStores::create_between(&root, "t", paths(), "fake-model", "bag", || more(&mut w)).err(),
|
||||
RunStores::create_between(&root, "t", paths(), "fake-model", "bag", weksa::run::LowererReach::ToolLessRelay, || more(&mut w)).err(),
|
||||
Some(RunError::Drift(DriftOf::Corpus))
|
||||
);
|
||||
assert!(!root.join("inventory.cc").exists());
|
||||
// Not the copy and not the lock the store's open left beside it.
|
||||
assert!(!root.join("inventory.cc.lock").exists());
|
||||
assert_eq!(RunStores::open(&root).err(), Some(RunError::NoRun));
|
||||
RunStores::create(&root, "t", paths(), "fake-model", "bag").unwrap();
|
||||
RunStores::create(&root, "t", paths(), "fake-model", "bag", weksa::run::LowererReach::ToolLessRelay).unwrap();
|
||||
}
|
||||
|
||||
/// Runs `change` in the window between the pins and the open, at `open` and at
|
||||
|
|
@ -411,7 +411,7 @@ fn window(
|
|||
inventory: &i,
|
||||
entities: &e,
|
||||
};
|
||||
let created = RunStores::create_between(&root, "t", paths, "fake-model", "bag", || {
|
||||
let created = RunStores::create_between(&root, "t", paths, "fake-model", "bag", weksa::run::LowererReach::ToolLessRelay, || {
|
||||
change(&mut w, &root)
|
||||
})
|
||||
.err();
|
||||
|
|
@ -458,9 +458,14 @@ fn source_digests_cover_their_files() {
|
|||
let read = |name: &str| std::fs::read(src.join(name)).unwrap();
|
||||
let digest = |name: &str| fnv1a64(&read(name));
|
||||
// The prompt's partition and names come from split.rs, and the lowering
|
||||
// prompt reads lower.rs's target language and entity port: all three are
|
||||
// covered.
|
||||
let (context, split, lower) = (read("context.rs"), read("split.rs"), read("lower.rs"));
|
||||
// prompt reads lower.rs's target language and entity port and prints
|
||||
// packet.rs's serde shapes: all four are covered.
|
||||
let (context, split, lower, packet) = (
|
||||
read("context.rs"),
|
||||
read("split.rs"),
|
||||
read("lower.rs"),
|
||||
read("packet.rs"),
|
||||
);
|
||||
// Each file enters behind its length, so the parts have one framing.
|
||||
let framed = [
|
||||
(context.len() as u64).to_le_bytes().as_slice(),
|
||||
|
|
@ -469,12 +474,14 @@ fn source_digests_cover_their_files() {
|
|||
&split,
|
||||
(lower.len() as u64).to_le_bytes().as_slice(),
|
||||
&lower,
|
||||
(packet.len() as u64).to_le_bytes().as_slice(),
|
||||
&packet,
|
||||
]
|
||||
.concat();
|
||||
assert_eq!(weksa::run::PROMPT_SOURCE_DIGEST, fnv1a64(&framed));
|
||||
assert_ne!(
|
||||
weksa::run::PROMPT_SOURCE_DIGEST,
|
||||
fnv1a64(&[context.clone(), split.clone(), lower.clone()].concat()),
|
||||
fnv1a64(&[context.clone(), split.clone(), lower.clone(), packet.clone()].concat()),
|
||||
"bare concatenation is not the digest"
|
||||
);
|
||||
// Moving the boundary between the parts moves the digest.
|
||||
|
|
@ -821,7 +828,7 @@ fn dao_over(edit: impl FnOnce(&Path, &Path)) -> Dao {
|
|||
entities: &entities,
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
.unwrap();
|
||||
Dao {
|
||||
|
|
@ -1470,15 +1477,15 @@ fn errors_echo_no_input() {
|
|||
inventory: &missing,
|
||||
entities: &missing,
|
||||
};
|
||||
let e = RunStores::create(&root, "ok", stores, "m", "bag")
|
||||
let e = RunStores::create(&root, "ok", stores, "m", "bag", weksa::run::LowererReach::ToolLessRelay)
|
||||
.err()
|
||||
.unwrap();
|
||||
note(&e, &e);
|
||||
let e = RunStores::create(&root, &format!("{canary}!"), stores, "m", "bag")
|
||||
let e = RunStores::create(&root, &format!("{canary}!"), stores, "m", "bag", weksa::run::LowererReach::ToolLessRelay)
|
||||
.err()
|
||||
.unwrap();
|
||||
note(&e, &e);
|
||||
let e = RunStores::create(&root, "ok", stores, &format!("{canary}\n"), "bag")
|
||||
let e = RunStores::create(&root, "ok", stores, &format!("{canary}\n"), "bag", weksa::run::LowererReach::ToolLessRelay)
|
||||
.err()
|
||||
.unwrap();
|
||||
note(&e, &e);
|
||||
|
|
@ -1512,7 +1519,7 @@ fn errors_echo_no_input() {
|
|||
entities: &dao.entities,
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
.unwrap();
|
||||
let mut model = cycle_fake(&format!("{{\"{canary}\":1}}"));
|
||||
|
|
@ -1570,7 +1577,7 @@ fn errors_echo_no_input() {
|
|||
);
|
||||
// The Prompts text names the three files its digest covers.
|
||||
let prompts = RunError::Drift(DriftOf::Prompts).to_string();
|
||||
assert!(["context.rs", "split.rs", "lower.rs"].iter().all(|f| prompts.contains(f)));
|
||||
assert!(["context.rs", "split.rs", "lower.rs", "packet.rs"].iter().all(|f| prompts.contains(f)));
|
||||
assert_eq!(format!("{:?}", RunError::Drift(DriftOf::Prompts)), prompts);
|
||||
}
|
||||
|
||||
|
|
@ -2442,7 +2449,7 @@ fn a_changed_head_is_drift() {
|
|||
entities: &dao.entities,
|
||||
},
|
||||
"fake-model",
|
||||
"bag",
|
||||
"bag", weksa::run::LowererReach::ToolLessRelay,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
|
|
@ -2670,6 +2677,13 @@ fn lowering_refusals_count_at_lower() {
|
|||
("empty text", Ok(r#"{"text":""}"#.to_string())),
|
||||
("blank text", Ok("{\"text\":\" \\n \"}".to_string())),
|
||||
("non-UTF-8", Err(PortError::Io(std::io::ErrorKind::InvalidData))),
|
||||
("NUL", Ok(serde_json::json!({ "text": "The sage\u{0}acts." }).to_string())),
|
||||
("bell", Ok(serde_json::json!({ "text": "The sage \u{7}acts." }).to_string())),
|
||||
(
|
||||
"over length",
|
||||
Ok(serde_json::json!({ "text": "a".repeat(weksa::encode::MAX_LOWERING_BYTES + 1) })
|
||||
.to_string()),
|
||||
),
|
||||
] {
|
||||
let r = run(raw);
|
||||
assert_eq!(r.train()[0].outcome, Outcome::Refused(Stage::Lower), "{what}");
|
||||
|
|
@ -2728,3 +2742,152 @@ fn an_added_entry_can_be_lowered() {
|
|||
Outcome::Scored { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_summary_line_says_how_lowerings_were_answered() {
|
||||
use weksa::run::LowererReach;
|
||||
assert_eq!("briefed-subagent".parse(), Ok(LowererReach::BriefedSubagent));
|
||||
assert_eq!("tool-less-relay".parse(), Ok(LowererReach::ToolLessRelay));
|
||||
assert!("Tool-Less-Relay".parse::<LowererReach>().is_err());
|
||||
let dao = dao();
|
||||
assert_eq!(
|
||||
dao.run.labelled("round 1"),
|
||||
"round 1 lowering=tool-less-relay"
|
||||
);
|
||||
// A briefed-subagent run is pinned as one, in the record that reopens, and
|
||||
// its lines say its numbers are smoke-round only.
|
||||
let root = dao.root.join("briefed");
|
||||
let paths = RunPaths {
|
||||
corpus: &dao.corpus,
|
||||
inventory: &dao.inventory,
|
||||
entities: &dao.entities,
|
||||
};
|
||||
RunStores::create(&root, "b", paths, "fake-model", "bag", LowererReach::BriefedSubagent)
|
||||
.unwrap();
|
||||
let run = RunStores::open(&root).unwrap();
|
||||
assert_eq!(run.record().lowerer_reach, LowererReach::BriefedSubagent);
|
||||
assert_eq!(
|
||||
run.labelled("cycle 1"),
|
||||
"cycle 1 lowering=briefed-subagent (smoke-round only)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lowering_text_at_the_bound_is_scored_and_line_breaks_are_text() {
|
||||
for text in [
|
||||
"a".repeat(weksa::encode::MAX_LOWERING_BYTES),
|
||||
"The sage acts.\n\tThe sage rests.".to_string(),
|
||||
] {
|
||||
let mut dao = dao();
|
||||
let mut model = fake(&good_answers());
|
||||
model.raw_lowering = Some(Ok(serde_json::json!({ "text": text }).to_string()));
|
||||
let r = recorded(round(&mut dao.run, &mut model, &Scorer::new(Bag)).unwrap());
|
||||
assert_eq!(
|
||||
(r.train_scored(), r.validation_scored()),
|
||||
(1, 2),
|
||||
"{}",
|
||||
text.len()
|
||||
);
|
||||
assert_eq!(r.validation_refused(), Default::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// Two canary items, one Train and one Validation, whose speaker is an entity
|
||||
/// with forms only in another language; each encodes to a packet citing it.
|
||||
#[test]
|
||||
fn a_lowering_prompt_that_cannot_be_built_refuses_at_lower_and_keeps_the_packet() {
|
||||
use weksa::entity::{Entity, EntityOrigin, LanguageForms};
|
||||
use weksa::packet::EntityId;
|
||||
let id = EntityId::parse("entity:sagexx").unwrap();
|
||||
let mut dao = dao_over(|corpus, entities| {
|
||||
let mut entity_store = weksa::EntityStore::open(entities).unwrap();
|
||||
entity_store
|
||||
.put(Entity {
|
||||
id: EntityId::parse("entity:sagexx").unwrap(),
|
||||
forms: vec![LanguageForms {
|
||||
language: "sa-Latn".into(),
|
||||
forms: vec!["Sagexa".into()],
|
||||
pronunciation: None,
|
||||
}],
|
||||
origin: EntityOrigin::Cast {
|
||||
persona: "sage".into(),
|
||||
fixture: "f.yaml".into(),
|
||||
},
|
||||
})
|
||||
.unwrap();
|
||||
let mut store = weksa::CorpusStore::open(corpus).unwrap();
|
||||
for (from, key) in [
|
||||
("item:daodejing:1", "item:cite:1"),
|
||||
("item:daodejing:2", "item:cite:2"),
|
||||
] {
|
||||
let mut item = store.get(from).unwrap().unwrap();
|
||||
item.id = key.into();
|
||||
item.text = format!("A held-apart sentence for {key}.");
|
||||
item.speaker = EntityId::parse("entity:sagexx").unwrap();
|
||||
item.names = vec![];
|
||||
item.prior_turns = vec![];
|
||||
store.add(item, &entity_store).unwrap();
|
||||
}
|
||||
});
|
||||
let citing = |n: usize| {
|
||||
let mut d = draft_of(n);
|
||||
d.referents[0].entity = Some(id.clone());
|
||||
json(&d)
|
||||
};
|
||||
let mut model = fake(&good_answers());
|
||||
model.answers.insert("item:cite:1".into(), citing(1));
|
||||
model.answers.insert("item:cite:2".into(), citing(2));
|
||||
let r = recorded(round(&mut dao.run, &mut model, &Scorer::new(Bag)).unwrap());
|
||||
// Admitted, so the packet is on the Train row; refused at Lower, so no score.
|
||||
let row = r.train().iter().find(|row| row.item == "item:cite:1").unwrap();
|
||||
assert_eq!(row.outcome, Outcome::Refused(Stage::Lower));
|
||||
let kept = row.packet.as_deref().expect("the admitted packet's key is kept");
|
||||
let packets = PacketStore::open(&dao.root.join("round-1/packets.cc")).unwrap();
|
||||
assert!(packets.get(kept).unwrap().is_some());
|
||||
let v = r.validation_refused();
|
||||
assert_eq!((v.parse, v.admit, v.lower), (0, 0, 1));
|
||||
assert_eq!((r.train_scored(), r.validation_scored()), (1, 2));
|
||||
// Five items encoded, three lowering prompts built and asked.
|
||||
assert_eq!(r.calls().len(), 5 + 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_port_failure_in_the_lowering_phase_discards_the_packets() {
|
||||
let mut dao = dao();
|
||||
let mut model = fake(&good_answers());
|
||||
model.raw_lowering = Some(Err(PortError::Io(std::io::ErrorKind::PermissionDenied)));
|
||||
assert_eq!(
|
||||
round(&mut dao.run, &mut model, &Scorer::new(Bag)).err(),
|
||||
Some(RunError::Io(std::io::ErrorKind::PermissionDenied))
|
||||
);
|
||||
assert!(!dao.root.join("round-1/packets.cc").exists());
|
||||
assert!(dao.run.rounds().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn views_with_one_meaning_share_one_unanswered_lowering_prompt() {
|
||||
let mut dao = dao();
|
||||
// Items 1 and 3 encode to the same meaning; item 2 differs.
|
||||
let same = json(&draft_of(1));
|
||||
let expected = prompts(&dao.run);
|
||||
let round1 = dao.root.join("round-1");
|
||||
std::fs::create_dir_all(&round1).unwrap();
|
||||
for (key, p) in &expected {
|
||||
let answer = match key.as_str() {
|
||||
"item:daodejing:2" => json(&draft_of(2)),
|
||||
_ => same.clone(),
|
||||
};
|
||||
std::fs::write(round1.join(format!("{}.answer", p.digest())), answer).unwrap();
|
||||
}
|
||||
let mut files = AnswerFiles::new(round1.clone(), "fake-model".into());
|
||||
let RoundOutcome::Unanswered { prompts: asked } =
|
||||
round(&mut dao.run, &mut files, &Scorer::new(Bag)).unwrap()
|
||||
else {
|
||||
panic!("answered lowering from nothing")
|
||||
};
|
||||
assert_eq!(asked.len(), 2, "{asked:?}");
|
||||
let mut unique = asked.clone();
|
||||
unique.sort();
|
||||
unique.dedup();
|
||||
assert_eq!(unique.len(), 2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ One line per cut; specs exist for the cuts about to run (2d and 4, siblings on `
|
|||
7a. `loop-context-builder`: `context.rs` (`TrainItems`, `EncoderOffer`, `Prompt`: corpus text reaches a model call only through `Prompt::mint` over live Train items or `Prompt::encode` over one offered item; instructions are the consts `MINT_INSTRUCTIONS` and `ENCODE_INSTRUCTIONS` in `context.rs`; `Checkpoint` has no public constructor, offers each item once, and its offers borrow it (the gate is typed `TestReadings` and Test offers on the round path; operator tools such as `weksa corpus dump` and `weksa score` still read corpus text of any split); prior turns print the held Train text), `SplitScheme::live`, `CorpusItem::cited_entities` (one owner of the id set `PacketStore::admit` and the offer share), and checkpoint-only Test readings (`Checkpoint`, `TestReadings`; `Aggregate` and `leak_diagnostic` carry no Test). Library only; since 7b `TrainItems::from_corpus` and `EncoderOffer::for_round` are crate-private and `RunStores` is their one public producer. Merges follow-ups `loop-context-builder-2` and `scorer-test-readings-type`. (spec r1)
|
||||
7b. `golf-loop-round` (spec r1; branch `weksa-lowering/golf-loop-round`): the smallest loop that scores one round. `run.rs`: `RunStores` pins the corpus and entity stores of a run by canonical path and FNV-1a 64 digest at `create`, reopens only from `<root>/run.cc` and refuses drift (corpus, entities, `PROMPT_SOURCE_DIGEST` of `context.rs`, a second pin on `lower.rs` retired in 7d, model, embedder); `round` encodes every live Train and Validation item through a `ModelPort` (`model.rs`; the one adapter `AnswerFiles` writes `<digest>.prompt` and reads `<digest>.answer`, so the model is the Claude session), reads each answer into a `PacketDraft` (`encode.rs`, the one place a model's JSON is read), admits through `PacketStore::admit` into `<root>/round-<n>/packets.cc`, lowers, scores once, and keeps a `RoundRecord` (`weksa.loop_round`, no text, no Test; Train rows, Validation only as means and refusal counts). `weksa loop open|round`. Not here: the spec-edit step, accept rule and composite, noise floor, Score and lowering documents, a subprocess adapter, the checkpoint door, a model lowerer (follow-ups `golf-loop-spec-edit`, `golf-loop-accept-rule`, `golf-loop-score-documents`, `golf-loop-subprocess-adapter`, `golf-loop-checkpoint-door-bound`, `golf-loop-model-lowerer`). `packet-store-inventory-binding` is met for loop packets only (`RunStores` admits with the run's inventory); `validate`, `packet proof|dao` and `lower` still take caller stores. Waits on 7a, `checkpoint-gate`, 4b.
|
||||
7c. `golf-loop-edit-accept` (spec r1; branch `weksa-lowering/golf-loop-edit-accept`): one edit-accept cycle. The run works on its own copy of the inventory (`create` copies the caller's file to `<root>/inventory.cc`, refusing a root that already holds a file of that name, and pins its digest as `RunRecord` key 11; `RunRecord.inventory` is provenance; a `run.cc` from before has no key 11 and does not open); the head is derived from the latest Accepted `Decision`. `weksa loop cycle` asks the editor for one inventory edit from `Prompt::mint(train, inventory, rev, feedback)` (Train blocks, the inventory listing and an `EditFeedback` line: whether the last edit was kept and the head's Validation composite rounded to `LADDER_STEP`, the Ladder release of Blum and Hardt 2015), reads it as an `EditDraft` (`encode.rs`; ids and revisions are the store's, `into_revision`), admits it through `InventoryStore::admit_revision` into `<root>/cycle-<k>/inventory.cc`, runs a candidate round under it, and `decide`s on Validation against the head's establishing round past a floor measured as the spread between rounds 1 and 2 (never under one `LADDER_STEP`). An edit that fails is a rejected `Decision`, not an error. `RoundRecord` gains keys 16 (inventory digest) and 17 (cycle); an encode answer that is not UTF-8 is that item's parse refusal. Not here: Score and lowering documents (`golf-loop-score-docs`), Train rows in the edit prompt (`golf-loop-edit-train-feedback`), a paired floor (`golf-loop-paired-floor`), the checkpoint door and merging to `data/` (`golf-loop-checkpoint-door-bound`, `golf-loop-freeze-merge`), a model lowerer (`golf-loop-model-lowerer-next`; until it lands an entry an edit adds has no lowering row, so a cycle gains only by retiring entries or adding ones the encoder does not cite). Waits on 7b.
|
||||
7d. `golf-loop-model-lowerer` (spec r1; branch `weksa-lowering/golf-loop-model-lowerer`): the round lowers through the `ModelPort`. `Prompt::lower` (`context.rs`) takes a `LoweringInput` (no text, key, group or speaker getter), the inventory at the view's revision and the en-US entity forms, and prints `LOWER_INSTRUCTIONS`, the meaning of the packet, the entities line and the inventory listing; the item key goes only to `item_keys`. The answer is `{"text"}`, read as `LoweringDraft` in `encode.rs`, held in `score_round`'s locals and scored once; it is never put in `run.cc`, a `RoundRecord`, a `Decision` or `EditFeedback`. A round has two answer phases (every encode prompt, then every lowering prompt), all or nothing: while any prompt is unanswered `packets.cc` is discarded and nothing reaches `run.cc`. `lower::lower` and the template table leave the loop (they remain `weksa lower`'s; follow-up `table-retirement`), so an entry an edit adds can be said and a cycle can buy a gain. One source pin remains: `PROMPT_SOURCE_DIGEST` covers `context.rs`, `split.rs` and `lower.rs` (`RunRecord` key 7 and `RoundRecord` key 3 are retired). Not here: per-item lowering text documents (`golf-loop-score-docs`), the subprocess adapter. Waits on 7c.
|
||||
7d. `golf-loop-model-lowerer` (spec r1; branch `weksa-lowering/golf-loop-model-lowerer`): the round lowers through the `ModelPort`. `Prompt::lower` (`context.rs`) takes a `LoweringInput` (no text, key, group or speaker getter), the inventory at the view's revision and the en-US entity forms, and prints `LOWER_INSTRUCTIONS`, the meaning of the packet, the entities line and the inventory listing; the item key goes only to `item_keys`. The answer is `{"text"}`, read as `LoweringDraft` in `encode.rs`, held in `score_round`'s locals and scored once; it is never put in `run.cc`, a `RoundRecord`, a `Decision` or `EditFeedback`. A round has two answer phases (every encode prompt, then every lowering prompt), all or nothing: while any prompt is unanswered `packets.cc` is discarded and nothing reaches `run.cc`. `lower::lower` and the template table leave the loop (they remain `weksa lower`'s; follow-up `table-retirement`), so an entry an edit adds can be said and a cycle can buy a gain. One source pin remains: `PROMPT_SOURCE_DIGEST` covers `context.rs`, `split.rs` and `lower.rs` (`RunRecord` key 7 and `RoundRecord` key 3 are retired). The run's record pins `LowererReach` (`weksa loop open --lowerer-reach briefed-subagent|tool-less-relay`, `RunRecord` key 12) and every printed round and cycle line says it; a briefed-subagent run says its numbers are smoke-round only (rulings `lowerer-call-reach-toolless-relay`, `dao-round-seed-inventory`). A lowering is refused at `Lower` when blank, over `MAX_LOWERING_BYTES` (4096) or holding a control character other than a line break or tab; `PROMPT_SOURCE_DIGEST` also covers `packet.rs`. Not here: per-item lowering text documents (`golf-loop-score-docs`), the subprocess adapter. Waits on 7c.
|
||||
7. `golf-loop-en` (7b and 7c landed the round, `ModelPort`, encoder prompt, template digest, round records and the edit-accept cycle; the rest remains): the packet store bound to one inventory store everywhere (`packet-store-inventory-binding`), a subprocess adapter, Score documents, `weksa score` in the loop, the detached unit on Yggdrasil. Waits on 3, 5, 6 and follow-up `claude-on-yggdrasil`.
|
||||
8. `style-axis`: a style-embedding score behind the scorer port. Waits on 3.
|
||||
9. `ja-panel`: ja-JP into the loop's panel. Waits on 7.
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"description": "The lowering prompt could not be built, or its answer was not UTF-8, not\na `LoweringDraft`, or empty text.",
|
||||
"description": "The lowering prompt could not be built, or its answer was not UTF-8, not\na `LoweringDraft`, or text that is blank, over `MAX_LOWERING_BYTES` or\nholds a control character.",
|
||||
"type": "string",
|
||||
"const": "Lower"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@
|
|||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"lowerer_reach": {
|
||||
"description": "How the run's lowering prompts are answered, set once at open.",
|
||||
"$ref": "#/$defs/LowererReach"
|
||||
},
|
||||
"model": {
|
||||
"description": "The answering model.",
|
||||
"type": "string"
|
||||
|
|
@ -55,6 +59,24 @@
|
|||
"model",
|
||||
"embedder",
|
||||
"chrf_version",
|
||||
"inventory_digest"
|
||||
]
|
||||
"inventory_digest",
|
||||
"lowerer_reach"
|
||||
],
|
||||
"$defs": {
|
||||
"LowererReach": {
|
||||
"description": "How a run's lowering prompts reach the model that answers them (rulings\n`lowerer-call-reach-toolless-relay`, `dao-round-seed-inventory`). The\nrecord says which, so a round lowered by a briefed subagent cannot pass as\na tool-less one.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A subagent briefed to read one prompt file: acceptable for smoke rounds\nonly.",
|
||||
"type": "string",
|
||||
"const": "BriefedSubagent"
|
||||
},
|
||||
{
|
||||
"description": "The prompt passed inline to an agent with no file tools.",
|
||||
"type": "string",
|
||||
"const": "ToolLessRelay"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue