pub struct AppConfig<CS: StateStoreBackend, A: AgentBackend, X: ActionBackend = NoActions, C: ConditionBackend = NoConditions, T: StateTyping = JsonState, H: LifecycleHooks = NoHooks, E: ChildExecutor = InlineExecutor> { /* private fields */ }Expand description
Complete application configuration.
App does not offer a separate built-in storage construction path:
let _ = app_kit::AppState::<storage_kit::StateStore>::Ephemeral;Owns caller-built storage, a homogeneous agent inventory, and workflow definitions.
The consuming with_* methods change their concrete App behavior parameter.
They do not erase backends or alter already admitted runs. Heterogeneous agent
inventories use a caller-owned exhaustive hierarchy. Dropping configuration
before open does not perform asynchronous Agent shutdown.
§Configure behavior
Start with ready storage, ready Agents, and parsed definitions. Apply the
consuming configuration methods before App::open; they retain exact
implementation types, including associated lower children and transaction
ownership. None registers a runtime callback or erases a backend.
| Behavior | Complete implementation example | Attach before open |
|---|---|---|
| Produce lazy action events and writes | workflow::ActionBackend | Self::with_actions |
| Evaluate declared routing predicates | workflow::ConditionBackend | Self::with_conditions |
| Construct and validate application state | workflow::StateTyping | Self::with_state_typing |
| Observe authoritative lifecycle events | workflow::LifecycleHooks | Self::with_hooks |
| Drive concrete local child futures | workflow::ChildExecutor | Self::with_child_executor |
The linked examples exercise each concrete behavior directly. Attach it before
opening App. Action and condition declarations must match the workflow’s
exact logical keys and revisions. A custom child executor must return every
admitted completion exactly once; it never creates child identities. App’s
built-in NoActions, NoConditions, JsonState, NoHooks and InlineExecutor
are suitable defaults only where their actual behavior satisfies the workflow.
Lower composition is independent: A::Workspace fixes its Conversation,
while CS::Transaction fixes consuming commit/rollback. For heterogeneous
Agent inventories implement matching caller-owned exhaustive hierarchies;
do not substitute public trait objects or independent child parameters.
See the local workflow.
Implementations§
Source§impl<CS: StateStoreBackend, A: AgentBackend> AppConfig<CS, A>
impl<CS: StateStoreBackend, A: AgentBackend> AppConfig<CS, A>
Sourcepub fn new(
state: CS,
agents: Vec<AgentSpec<A>>,
workflows: Vec<WorkflowDefinition>,
) -> Self
pub fn new( state: CS, agents: Vec<AgentSpec<A>>, workflows: Vec<WorkflowDefinition>, ) -> Self
Constructs configuration from a caller-owned control-plane store and agents.
The store is required even for process-local operation. App preserves its
concrete transaction type and never constructs lower products. Ownership
transfers to App when App::open is called.
See the local workflow.
Examples found in repository?
31 async fn open_app(&self) -> Result<App<StateStore, Agent>, AppError> {
32 let definition = WorkflowDefinition::parse(
33 &json!({
34 "schema":"app-kit.workflow/v2", "id":"history-counter", "revision":"r1",
35 "state":{"count":0}, "required":["count"],
36 "reducers":{"count":"reject_concurrent"}, "entry":"first",
37 "nodes":[
38 {"id":"first", "step":{"kind":"state", "mutations":[{
39 "kind":"increment", "target":"count",
40 "value":{"kind":"literal", "value":1}
41 }]}},
42 {"id":"second", "step":{"kind":"state", "mutations":[{
43 "kind":"increment", "target":"count",
44 "value":{"kind":"literal", "value":1}
45 }]}}
46 ],
47 "routes":{"first":{"mode":"first", "branches":[{
48 "target":{"kind":"node", "id":"second"}
49 }]}}
50 })
51 .to_string(),
52 )?;
53 // Agent supplies the static type for an empty inventory. No Agent value
54 // is constructed, and these native State nodes need no lower service.
55 App::open(AppConfig::<_, Agent>::new(
56 self.store.clone(),
57 Vec::new(),
58 vec![definition],
59 ))
60 .await
61 }More examples
23 async fn run() -> Result<(), Box<dyn Error + Send + Sync>> {
24 let definition = WorkflowDefinition::parse(
25 &json!({
26 "schema":"app-kit.workflow/v2", "id":"invoice", "revision":"r1",
27 "state":{"unit_price":7,"total":0}, "required":["unit_price","total"],
28 "reducers":{"unit_price":"reject_concurrent","total":"reject_concurrent"},
29 "entry":"calculate",
30 "nodes":[{"id":"calculate","step":{
31 "kind":"action","action":"invoice","revision":"r1","project_result":false
32 }}], "routes":{}
33 })
34 .to_string(),
35 )?;
36 let store =
37 StateStore::connect(StateStoreConfig::Lance(LanceStateStoreConfig::memory())).await?;
38 // Native caller actions need no Agent value or provider service.
39 let mut app = App::open(
40 AppConfig::<_, Agent>::new(store, Vec::new(), vec![definition])
41 .with_actions(vec![InvoiceAction::new()]),
42 )
43 .await?;
44 let observed = tokio::time::timeout(Duration::from_secs(15), async {
45 let workspace = app
46 .create_workspace(WorkspaceConfig {
47 name: Some("invoice example".into()),
48 root: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
49 })
50 .await?;
51 let workflow = workspace.workflow("invoice".parse()?).await?;
52 let mut run = workflow.prepare(WorkflowState::default()).await?;
53 let mut execution = run
54 .execution(ExecutionOptions {
55 inputs: Map::from_iter([
56 ("quantity".into(), json!(6)),
57 ("trace".into(), json!("sample")),
58 ]),
59 ..ExecutionOptions::default()
60 })
61 .await?;
62 let Some(ExecutionEvent::Partial { result, .. }) = execution.next().await? else {
63 return Err(AppError::Integration(
64 "expected the real action partial".into(),
65 ));
66 };
67 if result.get("progress") != Some(&json!("calculated")) {
68 return Err(AppError::Integration("partial changed".into()));
69 }
70 let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
71 return Err(AppError::Integration(
72 "expected one committed boundary".into(),
73 ));
74 };
75 let expected =
76 json!({"quantity":6,"label":"order","currency":"USD","trace":"sample","total":42});
77 if outcomes.len() != 1 || Value::Object(outcomes[0].result.clone()) != expected {
78 return Err(AppError::Integration(
79 "resolved inputs or action result changed".into(),
80 ));
81 }
82 let terminal = execution.finish().await?;
83 if !matches!(terminal.status, RunStatus::Succeeded)
84 || terminal.state.values()
85 != json!({"unit_price":7,"total":42}).as_object().unwrap()
86 {
87 return Err(AppError::Integration(
88 "only the explicit total write should persist".into(),
89 ));
90 }
91 println!(
92 "Partial: calculated; committed total: 42; transient inputs stayed out of state"
93 );
94 Ok::<(), AppError>(())
95 })
96 .await;
97 let closed = tokio::time::timeout(
98 Duration::from_secs(15),
99 app.shutdown(Shutdown::CancelAndDrain {
100 timeout: Duration::from_secs(10),
101 }),
102 )
103 .await;
104 drop(app);
105 observed.map_err(|_| AppError::Timeout("invoice execution timed out".into()))??;
106 closed.map_err(|_| AppError::Timeout("invoice cleanup timed out".into()))??;
107 Ok(())
108 }1054 async fn run(mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
1055 let manifest = if matches!(self.command, Command::ReopenHistory { .. }) {
1056 self.root = self.root.canonicalize()?;
1057 Some(self.read_manifest()?)
1058 } else {
1059 fs::create_dir(&self.root)?;
1060 self.root = self.root.canonicalize()?;
1061 fs::create_dir(self.root.join("workspace"))?;
1062 let mut marker = OpenOptions::new()
1063 .write(true)
1064 .create_new(true)
1065 .open(self.root.join(".performance-root"))?;
1066 marker.write_all(MANIFEST_SCHEMA.as_bytes())?;
1067 marker.sync_all()?;
1068 None
1069 };
1070 let actions = match self.command {
1071 Command::Linear => 1_000,
1072 Command::SeedHistory { rows } => (rows - 4) / 3,
1073 Command::Ready => 32 * 32,
1074 Command::Map => 4_096,
1075 Command::Repeated => 100,
1076 Command::NestedCancel => 16,
1077 Command::DelayedObserver => 1,
1078 Command::ReopenHistory { .. } => {
1079 manifest.as_ref().unwrap()["actions"].as_u64().unwrap()
1080 }
1081 };
1082 let linear = matches!(self.command, Command::Linear);
1083 let (definitions, definition_bytes) = self.definitions(actions)?;
1084 let probe = match self.command {
1085 Command::Ready => Some(WorkloadProbe::new(SourceMode::Permuted, 1_024)),
1086 Command::Map | Command::Repeated => {
1087 Some(WorkloadProbe::new(SourceMode::Parallel, actions as usize))
1088 }
1089 Command::NestedCancel => Some(WorkloadProbe::new(SourceMode::Held, 16)),
1090 Command::DelayedObserver => Some(WorkloadProbe::new(SourceMode::Partials, 1)),
1091 Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. } => None,
1092 };
1093 if let Some(manifest) = &manifest {
1094 assert_eq!(manifest["definition_bytes"], json!(definition_bytes));
1095 }
1096 let mut app = None;
1097 let started = Instant::now();
1098 let observed = AssertUnwindSafe(tokio::time::timeout(WORKLOAD_BUDGET, async {
1099 let connecting = Instant::now();
1100 let config = LanceStateStoreConfig::directory(self.root.join("state"))
1101 .with_operation_timeout(Duration::from_secs(30))?;
1102 let store = StateStore::connect(StateStoreConfig::Lance(config)).await?;
1103 let connect = connecting.elapsed();
1104 let opening = Instant::now();
1105 app = Some(
1106 App::open(
1107 AppConfig::<_, Agent>::new(store, Vec::new(), definitions).with_actions(
1108 probe
1109 .as_ref()
1110 .map(WorkloadAction::new)
1111 .into_iter()
1112 .collect(),
1113 ),
1114 )
1115 .await?,
1116 );
1117 let open = opening.elapsed();
1118 let mut result = match self.command {
1119 Command::Linear | Command::SeedHistory { .. } => {
1120 self.seed(app.as_ref().unwrap(), actions, linear).await?
1121 }
1122 Command::Ready => {
1123 self.ready(app.as_ref().unwrap(), probe.as_ref().unwrap())
1124 .await?
1125 }
1126 Command::Map => {
1127 self.mapped(app.as_ref().unwrap(), probe.as_ref().unwrap())
1128 .await?
1129 }
1130 Command::Repeated => {
1131 self.repeated(app.as_ref().unwrap(), probe.as_ref().unwrap())
1132 .await?
1133 }
1134 Command::NestedCancel => {
1135 self.nested_cancel(app.as_ref().unwrap(), probe.as_ref().unwrap())
1136 .await?
1137 }
1138 Command::DelayedObserver => {
1139 self.delayed_observer(app.as_ref().unwrap(), probe.as_ref().unwrap())
1140 .await?
1141 }
1142 Command::ReopenHistory { open_only } => {
1143 self.reopen(
1144 app.as_ref().unwrap(),
1145 manifest.as_ref().unwrap(),
1146 actions,
1147 open_only,
1148 )
1149 .await?
1150 }
1151 };
1152 result["store_connect_ns"] = json!(connect.as_nanos());
1153 result["app_open_ns"] = json!(open.as_nanos());
1154 result["workload_elapsed_ns"] = json!(started.elapsed().as_nanos());
1155 Ok::<_, Box<dyn Error + Send + Sync>>(result)
1156 }))
1157 .catch_unwind()
1158 .await;
1159 let success = matches!(&observed, Ok(Ok(Ok(_))));
1160 let closing = Instant::now();
1161 let cleanup = if let Some(mut app) = app.take() {
1162 let closed = AssertUnwindSafe(Self::close(&mut app, success))
1163 .catch_unwind()
1164 .await;
1165 drop(app);
1166 Some(closed)
1167 } else {
1168 None
1169 };
1170 if !success {
1171 eprintln!(
1172 "performance workload failed; cleanup completed={}",
1173 matches!(&cleanup, Some(Ok(Ok(()))))
1174 );
1175 }
1176 let mut result = match observed {
1177 Ok(Ok(result)) => result?,
1178 Ok(Err(_)) => {
1179 return Err(
1180 AppError::Timeout("performance workload budget exhausted".into()).into(),
1181 );
1182 }
1183 Err(panic) => resume_unwind(panic),
1184 };
1185 match cleanup {
1186 Some(Ok(result)) => result?,
1187 Some(Err(panic)) => resume_unwind(panic),
1188 None => return Err("successful workload did not retain its App cleanup owner".into()),
1189 }
1190 // All run/workflow/App owners and the caller-created store have dropped.
1191 // A cleanup timeout remains a failure even if reattachment later joined.
1192 result["schema"] = json!(MANIFEST_SCHEMA);
1193 result["settings"] = Self::settings(actions);
1194 if let Some(probe) = &probe {
1195 probe.assert_idle();
1196 result["caller_sources"] = probe.report();
1197 result["settings"]
1198 .as_object_mut()
1199 .unwrap()
1200 .remove("history_row_ceiling");
1201 result["settings"]["private_task_counts"] =
1202 json!("not exposed; App shutdown proves joined ownership");
1203 result["settings"]["lower_agents_and_tool_handlers"] = json!("none constructed");
1204 }
1205 result["definition_bytes"] = json!(definition_bytes);
1206 result["process_id"] = json!(std::process::id());
1207 result["os"] = json!(std::env::consts::OS);
1208 result["architecture"] = json!(std::env::consts::ARCH);
1209 result["package_version"] = json!(env!("CARGO_PKG_VERSION"));
1210 result["cleanup_joined"] = json!(true);
1211 result["cleanup_ns"] = json!(closing.elapsed().as_nanos());
1212 let encoded = serde_json::to_vec(&result)?;
1213 assert!(
1214 encoded.len() <= 16_384,
1215 "result stays within the manifest bound"
1216 );
1217 if matches!(self.command, Command::SeedHistory { .. }) {
1218 let mut file = OpenOptions::new()
1219 .write(true)
1220 .create_new(true)
1221 .open(self.root.join("manifest.json"))?;
1222 file.write_all(&encoded)?;
1223 file.sync_all()?;
1224 }
1225 println!("{}", std::str::from_utf8(&encoded)?);
1226 Ok(())
1227 }Source§impl<CS: StateStoreBackend, A: AgentBackend, X: ActionBackend, C: ConditionBackend, T: StateTyping, H: LifecycleHooks, E: ChildExecutor> AppConfig<CS, A, X, C, T, H, E>
impl<CS: StateStoreBackend, A: AgentBackend, X: ActionBackend, C: ConditionBackend, T: StateTyping, H: LifecycleHooks, E: ChildExecutor> AppConfig<CS, A, X, C, T, H, E>
Sourcepub fn with_actions<N: ActionBackend>(
self,
actions: Vec<N>,
) -> AppConfig<CS, A, N, C, T, H, E>
pub fn with_actions<N: ActionBackend>( self, actions: Vec<N>, ) -> AppConfig<CS, A, N, C, T, H, E>
Replaces the complete caller-owned action inventory before opening App.
Values retain their concrete type and need not implement Clone. App
validates their stable declarations before accepting any workflow work.
Replacing an inventory drops its previous values without invoking them.
Examples found in repository?
23 async fn run() -> Result<(), Box<dyn Error + Send + Sync>> {
24 let definition = WorkflowDefinition::parse(
25 &json!({
26 "schema":"app-kit.workflow/v2", "id":"invoice", "revision":"r1",
27 "state":{"unit_price":7,"total":0}, "required":["unit_price","total"],
28 "reducers":{"unit_price":"reject_concurrent","total":"reject_concurrent"},
29 "entry":"calculate",
30 "nodes":[{"id":"calculate","step":{
31 "kind":"action","action":"invoice","revision":"r1","project_result":false
32 }}], "routes":{}
33 })
34 .to_string(),
35 )?;
36 let store =
37 StateStore::connect(StateStoreConfig::Lance(LanceStateStoreConfig::memory())).await?;
38 // Native caller actions need no Agent value or provider service.
39 let mut app = App::open(
40 AppConfig::<_, Agent>::new(store, Vec::new(), vec![definition])
41 .with_actions(vec![InvoiceAction::new()]),
42 )
43 .await?;
44 let observed = tokio::time::timeout(Duration::from_secs(15), async {
45 let workspace = app
46 .create_workspace(WorkspaceConfig {
47 name: Some("invoice example".into()),
48 root: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
49 })
50 .await?;
51 let workflow = workspace.workflow("invoice".parse()?).await?;
52 let mut run = workflow.prepare(WorkflowState::default()).await?;
53 let mut execution = run
54 .execution(ExecutionOptions {
55 inputs: Map::from_iter([
56 ("quantity".into(), json!(6)),
57 ("trace".into(), json!("sample")),
58 ]),
59 ..ExecutionOptions::default()
60 })
61 .await?;
62 let Some(ExecutionEvent::Partial { result, .. }) = execution.next().await? else {
63 return Err(AppError::Integration(
64 "expected the real action partial".into(),
65 ));
66 };
67 if result.get("progress") != Some(&json!("calculated")) {
68 return Err(AppError::Integration("partial changed".into()));
69 }
70 let Some(ExecutionEvent::Committed { outcomes, .. }) = execution.step().await? else {
71 return Err(AppError::Integration(
72 "expected one committed boundary".into(),
73 ));
74 };
75 let expected =
76 json!({"quantity":6,"label":"order","currency":"USD","trace":"sample","total":42});
77 if outcomes.len() != 1 || Value::Object(outcomes[0].result.clone()) != expected {
78 return Err(AppError::Integration(
79 "resolved inputs or action result changed".into(),
80 ));
81 }
82 let terminal = execution.finish().await?;
83 if !matches!(terminal.status, RunStatus::Succeeded)
84 || terminal.state.values()
85 != json!({"unit_price":7,"total":42}).as_object().unwrap()
86 {
87 return Err(AppError::Integration(
88 "only the explicit total write should persist".into(),
89 ));
90 }
91 println!(
92 "Partial: calculated; committed total: 42; transient inputs stayed out of state"
93 );
94 Ok::<(), AppError>(())
95 })
96 .await;
97 let closed = tokio::time::timeout(
98 Duration::from_secs(15),
99 app.shutdown(Shutdown::CancelAndDrain {
100 timeout: Duration::from_secs(10),
101 }),
102 )
103 .await;
104 drop(app);
105 observed.map_err(|_| AppError::Timeout("invoice execution timed out".into()))??;
106 closed.map_err(|_| AppError::Timeout("invoice cleanup timed out".into()))??;
107 Ok(())
108 }More examples
1054 async fn run(mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
1055 let manifest = if matches!(self.command, Command::ReopenHistory { .. }) {
1056 self.root = self.root.canonicalize()?;
1057 Some(self.read_manifest()?)
1058 } else {
1059 fs::create_dir(&self.root)?;
1060 self.root = self.root.canonicalize()?;
1061 fs::create_dir(self.root.join("workspace"))?;
1062 let mut marker = OpenOptions::new()
1063 .write(true)
1064 .create_new(true)
1065 .open(self.root.join(".performance-root"))?;
1066 marker.write_all(MANIFEST_SCHEMA.as_bytes())?;
1067 marker.sync_all()?;
1068 None
1069 };
1070 let actions = match self.command {
1071 Command::Linear => 1_000,
1072 Command::SeedHistory { rows } => (rows - 4) / 3,
1073 Command::Ready => 32 * 32,
1074 Command::Map => 4_096,
1075 Command::Repeated => 100,
1076 Command::NestedCancel => 16,
1077 Command::DelayedObserver => 1,
1078 Command::ReopenHistory { .. } => {
1079 manifest.as_ref().unwrap()["actions"].as_u64().unwrap()
1080 }
1081 };
1082 let linear = matches!(self.command, Command::Linear);
1083 let (definitions, definition_bytes) = self.definitions(actions)?;
1084 let probe = match self.command {
1085 Command::Ready => Some(WorkloadProbe::new(SourceMode::Permuted, 1_024)),
1086 Command::Map | Command::Repeated => {
1087 Some(WorkloadProbe::new(SourceMode::Parallel, actions as usize))
1088 }
1089 Command::NestedCancel => Some(WorkloadProbe::new(SourceMode::Held, 16)),
1090 Command::DelayedObserver => Some(WorkloadProbe::new(SourceMode::Partials, 1)),
1091 Command::Linear | Command::SeedHistory { .. } | Command::ReopenHistory { .. } => None,
1092 };
1093 if let Some(manifest) = &manifest {
1094 assert_eq!(manifest["definition_bytes"], json!(definition_bytes));
1095 }
1096 let mut app = None;
1097 let started = Instant::now();
1098 let observed = AssertUnwindSafe(tokio::time::timeout(WORKLOAD_BUDGET, async {
1099 let connecting = Instant::now();
1100 let config = LanceStateStoreConfig::directory(self.root.join("state"))
1101 .with_operation_timeout(Duration::from_secs(30))?;
1102 let store = StateStore::connect(StateStoreConfig::Lance(config)).await?;
1103 let connect = connecting.elapsed();
1104 let opening = Instant::now();
1105 app = Some(
1106 App::open(
1107 AppConfig::<_, Agent>::new(store, Vec::new(), definitions).with_actions(
1108 probe
1109 .as_ref()
1110 .map(WorkloadAction::new)
1111 .into_iter()
1112 .collect(),
1113 ),
1114 )
1115 .await?,
1116 );
1117 let open = opening.elapsed();
1118 let mut result = match self.command {
1119 Command::Linear | Command::SeedHistory { .. } => {
1120 self.seed(app.as_ref().unwrap(), actions, linear).await?
1121 }
1122 Command::Ready => {
1123 self.ready(app.as_ref().unwrap(), probe.as_ref().unwrap())
1124 .await?
1125 }
1126 Command::Map => {
1127 self.mapped(app.as_ref().unwrap(), probe.as_ref().unwrap())
1128 .await?
1129 }
1130 Command::Repeated => {
1131 self.repeated(app.as_ref().unwrap(), probe.as_ref().unwrap())
1132 .await?
1133 }
1134 Command::NestedCancel => {
1135 self.nested_cancel(app.as_ref().unwrap(), probe.as_ref().unwrap())
1136 .await?
1137 }
1138 Command::DelayedObserver => {
1139 self.delayed_observer(app.as_ref().unwrap(), probe.as_ref().unwrap())
1140 .await?
1141 }
1142 Command::ReopenHistory { open_only } => {
1143 self.reopen(
1144 app.as_ref().unwrap(),
1145 manifest.as_ref().unwrap(),
1146 actions,
1147 open_only,
1148 )
1149 .await?
1150 }
1151 };
1152 result["store_connect_ns"] = json!(connect.as_nanos());
1153 result["app_open_ns"] = json!(open.as_nanos());
1154 result["workload_elapsed_ns"] = json!(started.elapsed().as_nanos());
1155 Ok::<_, Box<dyn Error + Send + Sync>>(result)
1156 }))
1157 .catch_unwind()
1158 .await;
1159 let success = matches!(&observed, Ok(Ok(Ok(_))));
1160 let closing = Instant::now();
1161 let cleanup = if let Some(mut app) = app.take() {
1162 let closed = AssertUnwindSafe(Self::close(&mut app, success))
1163 .catch_unwind()
1164 .await;
1165 drop(app);
1166 Some(closed)
1167 } else {
1168 None
1169 };
1170 if !success {
1171 eprintln!(
1172 "performance workload failed; cleanup completed={}",
1173 matches!(&cleanup, Some(Ok(Ok(()))))
1174 );
1175 }
1176 let mut result = match observed {
1177 Ok(Ok(result)) => result?,
1178 Ok(Err(_)) => {
1179 return Err(
1180 AppError::Timeout("performance workload budget exhausted".into()).into(),
1181 );
1182 }
1183 Err(panic) => resume_unwind(panic),
1184 };
1185 match cleanup {
1186 Some(Ok(result)) => result?,
1187 Some(Err(panic)) => resume_unwind(panic),
1188 None => return Err("successful workload did not retain its App cleanup owner".into()),
1189 }
1190 // All run/workflow/App owners and the caller-created store have dropped.
1191 // A cleanup timeout remains a failure even if reattachment later joined.
1192 result["schema"] = json!(MANIFEST_SCHEMA);
1193 result["settings"] = Self::settings(actions);
1194 if let Some(probe) = &probe {
1195 probe.assert_idle();
1196 result["caller_sources"] = probe.report();
1197 result["settings"]
1198 .as_object_mut()
1199 .unwrap()
1200 .remove("history_row_ceiling");
1201 result["settings"]["private_task_counts"] =
1202 json!("not exposed; App shutdown proves joined ownership");
1203 result["settings"]["lower_agents_and_tool_handlers"] = json!("none constructed");
1204 }
1205 result["definition_bytes"] = json!(definition_bytes);
1206 result["process_id"] = json!(std::process::id());
1207 result["os"] = json!(std::env::consts::OS);
1208 result["architecture"] = json!(std::env::consts::ARCH);
1209 result["package_version"] = json!(env!("CARGO_PKG_VERSION"));
1210 result["cleanup_joined"] = json!(true);
1211 result["cleanup_ns"] = json!(closing.elapsed().as_nanos());
1212 let encoded = serde_json::to_vec(&result)?;
1213 assert!(
1214 encoded.len() <= 16_384,
1215 "result stays within the manifest bound"
1216 );
1217 if matches!(self.command, Command::SeedHistory { .. }) {
1218 let mut file = OpenOptions::new()
1219 .write(true)
1220 .create_new(true)
1221 .open(self.root.join("manifest.json"))?;
1222 file.write_all(&encoded)?;
1223 file.sync_all()?;
1224 }
1225 println!("{}", std::str::from_utf8(&encoded)?);
1226 Ok(())
1227 }Sourcepub fn with_conditions<N: ConditionBackend>(
self,
conditions: Vec<N>,
) -> AppConfig<CS, A, X, N, T, H, E>
pub fn with_conditions<N: ConditionBackend>( self, conditions: Vec<N>, ) -> AppConfig<CS, A, X, N, T, H, E>
Replaces the complete caller-owned condition inventory before opening App.
App validates stable logical keys and revisions without evaluating the predicates. Replacing an inventory drops its previous values.
Sourcepub fn with_state_typing<N: StateTyping>(
self,
typing: N,
) -> AppConfig<CS, A, X, C, N, H, E>
pub fn with_state_typing<N: StateTyping>( self, typing: N, ) -> AppConfig<CS, A, X, C, N, H, E>
Replaces the caller-owned workflow typing policy before opening App.
The policy remains concrete and need not implement Clone. Its data
values are local to validation or execution and need not be Send.
Sourcepub fn with_hooks<N: LifecycleHooks>(
self,
hooks: N,
) -> AppConfig<CS, A, X, C, T, N, E>
pub fn with_hooks<N: LifecycleHooks>( self, hooks: N, ) -> AppConfig<CS, A, X, C, T, N, E>
Replaces the caller-owned lifecycle observer before opening App.
Replacement drops the prior observer without invoking it. The new
observer retains its concrete type and need not implement Clone.
Sourcepub fn with_child_executor<N: ChildExecutor>(
self,
executor: N,
) -> AppConfig<CS, A, X, C, T, H, N>
pub fn with_child_executor<N: ChildExecutor>( self, executor: N, ) -> AppConfig<CS, A, X, C, T, H, N>
Replaces the caller-owned admitted child executor before opening App.
The executor remains concrete. Its associated execution futures are
created only for admitted child work and need not implement Send.
Auto Trait Implementations§
impl<CS, A, X, C, T, H, E> Freeze for AppConfig<CS, A, X, C, T, H, E>
impl<CS, A, X, C, T, H, E> RefUnwindSafe for AppConfig<CS, A, X, C, T, H, E>where
CS: RefUnwindSafe,
Vec<AgentSpec<A>>: RefUnwindSafe,
Vec<X>: RefUnwindSafe,
Vec<C>: RefUnwindSafe,
T: RefUnwindSafe,
H: RefUnwindSafe,
E: RefUnwindSafe,
impl<CS, A, X, C, T, H, E> Send for AppConfig<CS, A, X, C, T, H, E>
impl<CS, A, X, C, T, H, E> Sync for AppConfig<CS, A, X, C, T, H, E>
impl<CS, A, X, C, T, H, E> Unpin for AppConfig<CS, A, X, C, T, H, E>
impl<CS, A, X, C, T, H, E> UnsafeUnpin for AppConfig<CS, A, X, C, T, H, E>where
CS: UnsafeUnpin,
Vec<AgentSpec<A>>: UnsafeUnpin,
Vec<X>: UnsafeUnpin,
Vec<C>: UnsafeUnpin,
T: UnsafeUnpin,
H: UnsafeUnpin,
E: UnsafeUnpin,
impl<CS, A, X, C, T, H, E> UnwindSafe for AppConfig<CS, A, X, C, T, H, E>where
CS: UnwindSafe,
Vec<AgentSpec<A>>: UnwindSafe,
Vec<X>: UnwindSafe,
Vec<C>: UnwindSafe,
T: UnwindSafe,
H: UnwindSafe,
E: UnwindSafe,
Blanket Implementations§
impl<T> Allocation for T
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::RequestSource§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.