Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9dca712
Remove SetRule, SetEvidence, and SetTool
vinniefalco Nov 3, 2026
dd86a20
Remove DepthVia from the call-depth failure
vinniefalco Nov 3, 2026
b682c1b
Add `place` and `coordinates` for error locations
vinniefalco Nov 3, 2026
126a8ea
Locate failures that reach Lua on the author's prompt line
vinniefalco Nov 3, 2026
c5c876d
Give Engine failures the prompt line that caused them
vinniefalco Nov 4, 2026
f2c5fc5
Give parse and run-start failures their prompt line
vinniefalco Nov 4, 2026
fc94388
Add error location invariants and correct stale comments
vinniefalco Nov 4, 2026
65c49c3
Remove `SpawnVia` and the cap name from the depth-cap error
vinniefalco Nov 4, 2026
539342b
Call the coroutine shim chunk a shim chunk, not a prelude
vinniefalco Nov 4, 2026
9524b89
Say which Engine globals `engine_globals` installs
vinniefalco Nov 4, 2026
bf50999
Share one copy of each error helper across Engine crates
vinniefalco Nov 4, 2026
9a4a152
Correct error-value docs and gate test-only store fields
vinniefalco Nov 4, 2026
cad74b1
Read universal error string fields from one table
vinniefalco Nov 4, 2026
d4cf401
Move chunk-line helpers into a `chunk_line` module
vinniefalco Nov 4, 2026
269b9e7
Replace `ModelReport` with `ModelCall` in model dispatch
vinniefalco Nov 4, 2026
3499d9f
Give the retained-error rule one home in the VM docs
vinniefalco Nov 4, 2026
6eeec80
Trim restated failure rules from the parse docs
vinniefalco Nov 4, 2026
67d1f08
Correct request docs and narrow two protocol items
vinniefalco Nov 4, 2026
7ca443a
Hold a task's live position in its running phase
vinniefalco Nov 4, 2026
4b577ce
Refuse tool_calls on a non-assistant record at the add
vinniefalco Nov 4, 2026
d9a0130
Correct models and models loop docs to match the code
vinniefalco Nov 4, 2026
dbf81f1
State which type-name rule each refusal uses
vinniefalco Nov 4, 2026
121a7b3
Rename private fields that collide with nearby names
vinniefalco Nov 4, 2026
4951eda
Move `DiscoveryTool` into `bindings` and narrow offers
vinniefalco Nov 4, 2026
05ebcad
Break three module cycles through `vm`
vinniefalco Nov 4, 2026
6afba74
Correct VM docs and drop `prose::install`
vinniefalco Nov 4, 2026
83bbb8e
Drop the alias module and keep the offer refusal private
vinniefalco Nov 4, 2026
fb94907
Move adhoc tools to the crate root and split the lookup
vinniefalco Nov 4, 2026
83a4fb2
Share one registry lookup and frozen guard in proxy
vinniefalco Nov 4, 2026
99473df
Rename the offers handle and the VM install helpers
vinniefalco Nov 4, 2026
56b9766
Raise the internal failure for poisoned locks in Lua
vinniefalco Nov 4, 2026
5a1f1f9
Correct the Lua crate docs and narrow `exhaustion_detail`
vinniefalco Nov 4, 2026
e84d52f
Correct small Lua module docs and reuse `cancelled`
vinniefalco Nov 4, 2026
9b5baa6
Exclude crate-private `Err` markers from the error rule
vinniefalco Nov 4, 2026
983ad12
Drop the model handle loop from the install_coro_shims doc
vinniefalco Nov 4, 2026
37019f6
Drop model handle methods from Engine docs and tests
vinniefalco Nov 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Seven terms have exactly one meaning each, everywhere in this repository: code c
- **Host**: an application that runs prompts through the Harness, such as Workshop or Papergate. The Host makes every policy decision. It runs prompts only through the Harness; it may also use the Engine's parser and types to read prompts and show events.
- **Pack**: code which installs an optional native prelude, an optional Lua prelude, and zero or more tools.
- **Plugin**: an entry point which installs zero or more Packs.
- **Static Set**: the tools a section entry renders into the model's tool list at its first loop round, the first round a `models.loop` or a model handle's `loop` sends. It is frozen after that.
- **Static Set**: the tools a section entry renders into the model's tool list at its first loop round, the first round a `models.loop` call sends. It is frozen after that.
- **Dynamic Set**: the tools the model finds and calls through the Engine's two discovery tools.

### Using the terms
Expand Down Expand Up @@ -47,7 +47,7 @@ Seven terms have exactly one meaning each, everywhere in this repository: code c
- A Cargo feature gates a real constraint such as a toolchain requirement or heavy native build. It does not describe product shape. Feature-disabled builds must not leak optional types into core paths.
- Runtime and serve paths never compile native dependencies or invoke build tools.
- Unsafe code stays in its explicitly owned boundary.
- Engine and Harness crates report every failure as the universal `Error` in `runvil-types`, which the `runvil` facade re-exports. A new kind of failure is a new `ErrorCode` variant in its hundreds family, with a number never used before, plus a `FactKey` variant only when no existing key names the value. They add no other error enum or struct, no `thiserror` derive, and no other `impl std::error::Error`; a foreign cause joins the message through `Error::with_cause`. `VfsError` and `ParsePathReasonError` in `runvil-vfs` are the only exceptions, and test code may define a throwaway error type to exercise `with_cause`.
- Engine and Harness crates report every failure as the universal `Error` in `runvil-types`, which the `runvil` facade re-exports. A new kind of failure is a new `ErrorCode` variant in its hundreds family, with a number never used before, plus a `FactKey` variant only when no existing key names the value. They add no other error enum or struct, no `thiserror` derive, and no other `impl std::error::Error`; a foreign cause joins the message through `Error::with_cause`. `VfsError` and `ParsePathReasonError` in `runvil-vfs` are the only exceptions, and test code may define a throwaway error type to exercise `with_cause`. A crate-private marker returned as `Err` and turned into `Error` by its caller, or a type that pairs an `Error` with a value the caller must still release, such as `InjectFailure`, is not another error type.
- Comments explain a non-obvious constraint, ordering requirement, or workaround. Every platform or external-bug workaround cites its upstream issue URL in the explanatory comment.
- JSON that reaches a recorder or a replay comparison round-trips exactly - `to_value`, `to_string`, `from_str` yield an identical value, object keys stay canonical (sorted), numbers must be finite. Exact parsing (`float_roundtrip`) carries that guarantee; a value derived and then logged is additionally rounded to its meaningful precision at the source.

Expand Down
7 changes: 4 additions & 3 deletions crates/harness-internal/runner/src/host-tests-pick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn a_bare_entry_that_only_another_vendor_has_is_unavailable_naming_each_fqn() {
refusal(&run, &declares),
"the environment cannot satisfy this prompt:\n\
- poop is unavailable: poop means runvil/poop, which is not installed; \
write acme/poop to use that Pack",
write acme/poop to use that Pack (at prompt:5)",
"the run names the reason and does not call the Pack missing"
);

Expand Down Expand Up @@ -116,7 +116,7 @@ fn an_entry_no_vendor_has_is_missing_and_an_absent_vendor_name_is_unavailable()
refusal(&run, &declares),
"the environment cannot satisfy this prompt:\n\
- missing required Pack: nope\n\
- gone is unavailable: acme/gone is not installed"
- gone is unavailable: acme/gone is not installed (at prompt:5)"
);
}

Expand Down Expand Up @@ -152,7 +152,8 @@ fn the_roster_covers_every_declared_pack_the_run_cannot_serve() {
"the environment cannot satisfy this prompt:\n\
- missing required Pack: absent\n\
- needy is unavailable: the run lacks the services it needs: tests/session\n\
- broken is unavailable: backed needs tests/backend, and this host provides none"
- broken is unavailable: backed needs tests/backend, and this host provides none \
(at prompt:5)"
);
}

Expand Down
3 changes: 2 additions & 1 deletion crates/harness-internal/runner/src/host-tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ fn a_construct_failure_is_stored_and_reported_as_unavailable_with_its_reason() {
assert_eq!(
refusal(&run, &declares),
"the environment cannot satisfy this prompt:\n\
- backed is unavailable: backed needs tests/backend, and this host provides none",
- backed is unavailable: backed needs tests/backend, and this host provides none \
(at prompt:5)",
"the run names the reason, and never calls the built Pack missing"
);

Expand Down
6 changes: 3 additions & 3 deletions crates/harness-internal/runner/tests/it/prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,14 @@ async fn an_unmet_requirement_ends_the_prepared_run_as_failed_with_the_engines_n
let (run_id, outcome) = prepare_and_drive(NEEDS_WEB, services(&recorder, bare())).await;

let notice = "the environment cannot satisfy this prompt:\n\
- missing required Pack: web";
- missing required Pack: web (at prompt:5)";
assert_eq!(
outcome,
RunOutcome::Failed {
kind: RunErrorKind::RequirementsUnmet,
message: notice.to_owned(),
},
"the failure is the engine's notice, verbatim"
"the failure is the engine's notice, verbatim, on the `uses:` line"
);
assert_eq!(
recorder.outcome(run_id),
Expand Down Expand Up @@ -460,7 +460,7 @@ async fn a_bare_entry_another_vendor_answers_is_refused_naming_its_fqn() {
refusal(drive_over(declares_bare, building(TOOLS)).await),
"the environment cannot satisfy this prompt:\n\
- tools is unavailable: tools means runvil/tools, which is not installed; \
write tests/tools to use that Pack",
write tests/tools to use that Pack (at prompt:5)",
"a bare id means the runvil Pack, which the Host lacks"
);
}
Expand Down
91 changes: 15 additions & 76 deletions crates/runvil-internal/engine/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,76 +20,23 @@ use std::borrow::Cow;
#[cfg(test)]
pub(crate) use runvil_lua::{context_exhausted, lua_quota, tool_loop_exhausted};
pub(crate) use runvil_lua::{
interrupted, lua_runtime, task_cancelled, task_consumed, task_not_owned,
internal, interrupted, lua_runtime, task_cancelled, task_consumed, task_not_owned,
};
pub(crate) use runvil_types::error::Error;
use runvil_types::error::{ErrorCode, FactKey};
use runvil_types::error::{ErrorCode, FactKey, failure, with_fact};
use runvil_types::ids::TaskId;
use serde_json::Value;

mod sets;
#[cfg(test)]
mod tests;
mod tools;

pub(crate) use sets::{SetEvidence, SetRule, SetTool, tool_sets};
pub(crate) use tools::{bind_schema, model_only_tool, out_of_scope_tool, unbound_tool};
pub(crate) use tools::{
bind_schema, model_only_tool, out_of_scope_tool, tool_in_set, tool_sets, unbound_tool,
};

/// Crate-internal result alias over [`Error`].
pub(crate) type Result<T> = std::result::Result<T, Error>;

/// Builds a failure with `code`, `message`, and the code's default prose.
fn failure(code: ErrorCode, message: impl Into<String>) -> Error {
Error::new(code, message, code.prose())
}

/// Gives `error` the fact `key` with `value`.
fn with_fact(mut error: Error, key: FactKey, value: impl Into<Value>) -> Error {
error.facts.insert(key, value.into());
error
}

/// The call that passed the call-depth cap, which a `CallDepthExceeded`
/// failure names. The name is the cap's, not the author's construct:
/// `tasks.fanout` for a fanout arm, and `call` for a `call` or a
/// `tasks.spawn`, which shares its cap.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DepthVia {
/// A `call`, or a `tasks.spawn`.
Call,
/// One arm of a `tasks.fanout`.
Fanout,
}

impl DepthVia {
/// The failure's `Reason` fact: `call` or `fanout`.
fn tag(self) -> &'static str {
match self {
DepthVia::Call => "call",
DepthVia::Fanout => "fanout",
}
}
}

impl std::fmt::Display for DepthVia {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
DepthVia::Call => "call",
DepthVia::Fanout => "tasks.fanout",
})
}
}

/// An internal invariant failure: a state the surrounding code has already
/// guaranteed cannot occur, surfaced rather than skipped so it cannot pass
/// for a successful fall-through. Its location is the Rust source position
/// of the call site, which the run's failure message ends with. The message
/// is owned when it names a value.
#[track_caller]
pub(crate) fn internal(message: impl Into<Cow<'static, str>>) -> Error {
runvil_lua::internal_at(message, std::panic::Location::caller())
}

/// An internal invariant failure that names the failure which broke the
/// invariant after its own text, capturing the call site as [`internal`]
/// does.
Expand Down Expand Up @@ -141,18 +88,17 @@ pub(crate) fn model_required(section: &str) -> Error {
}

/// A `call`, `tasks.spawn`, or `tasks.fanout` arm that would run deeper
/// than `cap`, through `via`. The dispatch's answer, so an author `pcall`
/// catches it, and a quota when it ends the run.
pub(crate) fn call_depth_exceeded(cap: usize, via: DepthVia) -> Error {
let error = with_fact(
/// than `cap`. The dispatch's answer, so an author `pcall` catches it, and
/// a quota when it ends the run.
pub(crate) fn call_depth_exceeded(cap: usize) -> Error {
with_fact(
failure(
ErrorCode::CallDepthExceeded,
format!("{via} recursion exceeded cap of {cap}"),
format!("recursion exceeded cap of {cap}"),
),
FactKey::Reason,
via.tag(),
);
with_fact(error, FactKey::Limit, cap)
FactKey::Limit,
cap,
)
}

/// A chain that ended while the tasks it owned were still live, which it
Expand All @@ -172,20 +118,13 @@ pub(crate) fn tasks_live(tasks: &[TaskId]) -> Error {
/// and a message that tells a run that declares no store so and follows
/// the fixed probe text for any other failure.
pub(crate) fn store(cause: &runvil_vfs::VfsError) -> Error {
store_with_message(cause, crate::lua::store_probe_message(cause))
runvil_lua::store_failure(cause, crate::lua::store_probe_message(cause))
}

/// One store operation's failure: the store's code and facts, and the
/// model-facing message rendered with the operation for its wording.
pub(crate) fn store_op(op: &crate::lua::VfsOp, cause: &runvil_vfs::VfsError) -> Error {
store_with_message(cause, crate::lua::store_error_message(op, cause))
}

/// The store failure `cause` with `message` in place of its own text.
fn store_with_message(cause: &runvil_vfs::VfsError, message: String) -> Error {
let mut error = runvil_lua::store_error(cause);
error.message = message;
error
runvil_lua::store_failure(cause, crate::lua::store_error_message(op, cause))
}

/// Two execution identities that claimed one store path: `detail` is the
Expand Down
144 changes: 0 additions & 144 deletions crates/runvil-internal/engine/src/error/sets.rs

This file was deleted.

9 changes: 5 additions & 4 deletions crates/runvil-internal/engine/src/error/tests-chain.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! The cause convention: a failure built with a cause holds the cause's
//! text once in its message, after its own, and keeps its prose free of
//! it; a model failure's table holds only its prose; the Lua crate's
//! failures end a run as they always did; and the store probe names a
//! missing store.
//! failures end a run with their own kind and text, a located one's
//! ending with where it failed; and the store probe names a missing
//! store.

use runvil_types::error::ErrorCode;
use runvil_vfs::VfsError;
Expand Down Expand Up @@ -93,7 +94,7 @@ fn a_model_failure_holds_its_cause_in_the_run_message_and_not_in_its_table() {
}

#[test]
fn the_lua_crates_failures_end_a_run_with_the_kind_and_text_they_always_had() {
fn the_lua_crates_failures_end_a_run_with_their_own_kind_and_text() {
let site = std::panic::Location::caller();
let emitter = crate::test_support::recording::null_emitter();
let compiled = runvil_lua::LuaProgram::compile(
Expand Down Expand Up @@ -130,7 +131,7 @@ fn the_lua_crates_failures_end_a_run_with_the_kind_and_text_they_always_had() {
compiled,
RunErrorKind::Lua,
"lua compilation error at section `S` prologue (line 7): syntax error: \
[string \"line 7\"]:1: unexpected symbol near <eof>"
[string \"line 7\"]:1: unexpected symbol near <eof> (at prompt:7)"
.to_owned(),
),
(
Expand Down
Loading
Loading