1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
//! Section lifecycle execution and fall-through.
//!
//! The run walks top-level sections in file order, creating one isolated
//! section VM for each. Shared Lua loads before host injection,
//! then ordered section blocks use that same VM. Lua before the first prose is
//! prologue-style; Lua after is epilog-style. Non-final prose is single-shot;
//! final prose runs the full tool loop. A scalar early Lua return ends the
//! section; a scalar late Lua return ends the run.
//!
//! Running off the last section ends the run: the result is `default_return`
//! from the frontmatter, else the last model reply, else a generic completion.
//!
//! One run-scoped [`StoreRef`] is created once by the caller and threaded through
//! every section (both its Lua prologue and, later, the model's file tools), so
//! bulk state persists across the context-clearing transitions even though a
//! section's conversation never does.
//!
//! A run reports itself as it goes: the [`RunConfig`] observer receives a
//! `(execution, section, event)` record when the run starts and ends, at each
//! section boundary, model turn, tool call, and harness-mediated store
//! operation. Reporting is a side channel and never
//! a decision, so passing [`crate::observe::NullObserver`] changes nothing but
//! the silence.
//!
//! Rust installs tool bindings captured from live H1 into each section VM.
//! Prompt-wide aliases and H2 additions form the effective model-visible scope,
//! which is checked for semantic near-duplicates before concrete tools are
//! advertised under their local aliases and dispatched by stable identity.
//!
//! Lua `execute()` runs a named top-level section as a subroutine (fresh VM,
//! fresh conversation, recursion capped at 8) and returns that section's reply.
//! Lua `jump(target)` transfers control to a named section and clears
//! cross-section reply context.
//!
//! # Module layout
//!
//! The orchestration boundary ([`run`]) lives here; the rest is split into
//! focused private children: `error` (the public [`RunError`]), `config`
//! (`RunConfig`/`RunLimits`), `gateway` (client acquisition and
//! [`ResolutionContext`]), `scope` (tool-scope analysis and validation),
//! `tools` (the `model:infer` bag and hook), `tool_loop` (the model tool
//! loop), `h1` (the live H1 pass), `engine` (the section walkers), and
//! `support` (the sync/async bridge and shared helpers).
use Arc;
use AtomicU32;
// Public API surface.
pub use ;
pub use ;
pub use ResolutionContext;
// Crate-internal items reused by `fanout` (through the historical
// `crate::execute::` path) and by `run` below. Re-exported so the split stays
// surface-neutral for the public API while keeping one import path for
// internal collaborators.
pub use run_sections;
pub use execute_live_h1;
pub use ;
pub use now_rfc3339_checked;
pub use ;
// Executor-internal items and crate types the executor's own tests reach
// through `use super::super::*` (and that `tests/mod.rs` does not itself
// import). Test-only, so the non-test lib carries no unused re-export while the
// historic executor namespace stays intact for the test glob.
pub use crateResult;
pub use crateToolSchema;
pub use crate;
pub use crateModelBindings;
pub use crateObserver;
pub use ;
pub use OwnedNearDuplicate;
pub use json;
pub use BTreeMap;
pub use ;
pub use ToolBag;
use crateError;
use cratecancel;
use crateDebugCapture;
use cratedetail;
use cratePrompt;
use crateStoreRef;
use crate;
use SUPPORTED_MAJOR;
/// Executes a parsed prompt and returns its final text.
///
/// H1 Lua and prose blocks run once in source order with full host access;
/// capability calls resolve when executed. If H1 does not return, the H2 section
/// walk runs and its accumulated text is returned.
///
/// # Errors
/// Returns a [`RunError`] whose [`kind`](RunError::kind) classifies the failure
/// by condition:
/// - [`RunErrorKind::Parse`] - a prompt/frontmatter or compiled Lua region was
/// invalid.
/// - [`RunErrorKind::Version`] - the prompt declared an unsupported
/// `promptforge:` major, or omitted it.
/// - [`RunErrorKind::Binding`] - a `tools.need`/`models.need` capability could
/// not be bound, was absent, or clashed.
/// - [`RunErrorKind::Completion`] - a model completion failed at the transport,
/// backend, decode, or dialect layer.
/// - [`RunErrorKind::Tool`] - a dispatched tool failed, was out of scope, or the
/// tool loop did not converge.
/// - [`RunErrorKind::Lua`] - a section's Lua phase failed to run or return a
/// usable value.
/// - [`RunErrorKind::Quota`] - a Lua host resource quota (log events, log bytes,
/// or instructions) was exhausted.
/// - [`RunErrorKind::Substitution`] - a `{{ }}` prose substitution failed.
/// - [`RunErrorKind::Store`] - a run-scoped store operation failed.
/// - [`RunErrorKind::Cancelled`] - the host cancelled the run.
/// - [`RunErrorKind::Internal`] - an internal invariant failed (for example a
/// Lua host call reached on a current-thread runtime, which returns this
/// rather than panicking).
///
/// # Examples
/// A no-network, Lua-only prompt whose H1 block returns a value:
/// ```
/// use promptforge_core::execute::{run, RunConfig, ResolutionContext};
/// use promptforge_core::model::ModelCatalog;
/// use promptforge_core::observe::NullObserver;
/// use promptforge_core::parser::Prompt;
/// use promptforge_core::store::StoreRef;
/// use promptforge_tool_picker::{Catalog, Config, ToolPicker};
///
/// let source = concat!(
/// "---\nname: t\ndescription: d\npromptforge: 1\n---\n\n",
/// "# Title\n\n",
/// "```lua\nreturn 'hello'\n```\n\n",
/// "## Only\n\ndone\n",
/// );
/// let prompt = Prompt::parse(source, "doc-example", &NullObserver::default())?;
/// let picker = ToolPicker::build(Catalog::new(Vec::new()), Config::default())?;
/// let models = ModelCatalog::empty();
///
/// let runtime = tokio::runtime::Builder::new_current_thread().build()?;
/// let output = runtime.block_on(run(
/// &prompt,
/// "",
/// ResolutionContext::new(&picker, &models),
/// &[],
/// &StoreRef::memory(),
/// RunConfig::new("doc-example"),
/// ))?;
/// assert_eq!(output, "hello");
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Runtime
/// Nested Lua host calls (`model:infer`, `execute`, `fanout`) bridge synchronous
/// Lua into async work via `tokio::task::block_in_place`, which requires the
/// multi-threaded Tokio runtime. Reaching such a call on a current-thread
/// runtime returns [`RunErrorKind::Internal`] rather than panicking (a prompt
/// with no nested host calls, like the example above, runs on either runtime).
pub async