Skip to main content

ag_agent/agent/
instruction.rs

1//! Provider-managed instruction bootstrap planning for app-server sessions.
2
3use crate::channel::AgentRequestKind;
4
5/// Normalizes one provider-native conversation id for persisted bootstrap
6/// reuse tracking.
7pub fn normalize_instruction_conversation_id(
8    provider_conversation_id: Option<&str>,
9) -> Option<String> {
10    provider_conversation_id
11        .map(str::trim)
12        .filter(|value| !value.is_empty())
13        .map(ToString::to_string)
14}
15
16/// Prompt-shaping mode used for one app-server turn attempt.
17#[derive(Debug, Clone, Copy, Eq, PartialEq)]
18pub enum InstructionDeliveryMode {
19    /// Send the full instruction contract without transcript replay.
20    BootstrapFull,
21    /// Reuse the existing provider-managed bootstrap and send only a compact
22    /// reminder.
23    DeltaOnly,
24    /// Re-send the full instruction contract while replaying the transcript
25    /// after context loss.
26    BootstrapWithReplay,
27}
28
29/// Plans how one app-server turn should deliver Agentty's instruction
30/// contract.
31pub fn plan_app_server_instruction_delivery(
32    request_kind: &AgentRequestKind,
33    current_provider_conversation_id: Option<&str>,
34    persisted_instruction_conversation_id: Option<&str>,
35    should_replay_transcript: bool,
36) -> InstructionDeliveryMode {
37    if should_replay_transcript {
38        return InstructionDeliveryMode::BootstrapWithReplay;
39    }
40
41    if matches!(
42        request_kind,
43        AgentRequestKind::UtilityPrompt | AgentRequestKind::AccountRead
44    ) {
45        return InstructionDeliveryMode::BootstrapFull;
46    }
47
48    if normalize_instruction_conversation_id(current_provider_conversation_id).as_deref()
49        == persisted_instruction_conversation_id
50    {
51        return InstructionDeliveryMode::DeltaOnly;
52    }
53
54    InstructionDeliveryMode::BootstrapFull
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    /// Reuses the provider-managed bootstrap only when the persisted state
63    /// still matches the active provider conversation.
64    fn test_plan_app_server_instruction_delivery_uses_delta_only_for_matching_state() {
65        // Arrange
66        let persisted_instruction_conversation_id =
67            normalize_instruction_conversation_id(Some("thread-123"));
68
69        // Act
70        let mode = plan_app_server_instruction_delivery(
71            &AgentRequestKind::SessionResume,
72            Some("thread-123"),
73            persisted_instruction_conversation_id.as_deref(),
74            false,
75        );
76
77        // Assert
78        assert_eq!(mode, InstructionDeliveryMode::DeltaOnly);
79    }
80
81    #[test]
82    /// Forces a replay bootstrap whenever the runtime lost provider-managed
83    /// context for the active turn.
84    fn test_plan_app_server_instruction_delivery_uses_bootstrap_with_replay_after_reset() {
85        // Arrange
86        let persisted_instruction_conversation_id =
87            normalize_instruction_conversation_id(Some("thread-123"));
88
89        // Act
90        let mode = plan_app_server_instruction_delivery(
91            &AgentRequestKind::SessionResume,
92            Some("thread-456"),
93            persisted_instruction_conversation_id.as_deref(),
94            true,
95        );
96
97        // Assert
98        assert_eq!(mode, InstructionDeliveryMode::BootstrapWithReplay);
99    }
100
101    #[test]
102    /// Requires a fresh bootstrap when the provider conversation changed.
103    fn test_plan_app_server_instruction_delivery_bootstraps_full_for_new_context() {
104        // Arrange
105        let persisted_instruction_conversation_id =
106            normalize_instruction_conversation_id(Some("thread-123"));
107
108        // Act
109        let mode = plan_app_server_instruction_delivery(
110            &AgentRequestKind::SessionResume,
111            Some("thread-456"),
112            persisted_instruction_conversation_id.as_deref(),
113            false,
114        );
115
116        // Assert
117        assert_eq!(mode, InstructionDeliveryMode::BootstrapFull);
118    }
119
120    #[test]
121    /// Keeps one-shot utility prompts on the full bootstrap path because they
122    /// do not reuse long-lived provider context.
123    fn test_plan_app_server_instruction_delivery_bootstraps_full_for_utility_prompt() {
124        // Arrange
125        let persisted_instruction_conversation_id =
126            normalize_instruction_conversation_id(Some("thread-123"));
127
128        // Act
129        let mode = plan_app_server_instruction_delivery(
130            &AgentRequestKind::UtilityPrompt,
131            Some("thread-123"),
132            persisted_instruction_conversation_id.as_deref(),
133            false,
134        );
135
136        // Assert
137        assert_eq!(mode, InstructionDeliveryMode::BootstrapFull);
138    }
139}