Skip to main content

harness_rs_core/
context.rs

1use crate::{ModelOutput, Signal};
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4
5/// A single block of content within the assembled prompt.
6///
7/// Blocks are grouped so that long-stable prefixes (system + guides) stay
8/// cacheable across turns ("prompt caching" pattern).
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[non_exhaustive]
11pub enum Block {
12    /// Plain prompt text.
13    Text(String),
14    /// Reference to a file in the world. The runtime decides whether to
15    /// inline contents or hand the agent a tool call to read it.
16    FileRef { path: String, hash: Option<String>, excerpt: Option<String> },
17    /// Reference to an activated SKILL.md body.
18    Skill   { name: String, body: String },
19    /// A tool call the assistant requested.
20    ToolCall { call_id: String, name: String, args: serde_json::Value },
21    /// The result of a previous tool call.
22    ToolResult { call_id: String, content: serde_json::Value },
23    /// Feedback signals from sensors, rendered for the model.
24    Feedback(Vec<Signal>),
25    /// Provider-specific reasoning trace (DeepSeek `reasoning_content`,
26    /// Anthropic `thinking` blocks). Must be echoed back to the provider on
27    /// subsequent calls or the API rejects the request.
28    Reasoning(String),
29}
30
31/// A single conversation turn (assistant or user).
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct Turn {
34    pub role:    TurnRole,
35    pub blocks:  Vec<Block>,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "lowercase")]
40#[non_exhaustive]
41pub enum TurnRole {
42    User,
43    Assistant,
44    System,
45    Tool,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Task {
50    pub description: String,
51    pub source:      Option<String>,      // slack url, github issue, etc.
52    pub deadline:    Option<i64>,
53}
54
55#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
56pub struct Policy {
57    pub max_iters:           u32,
58    pub max_input_tokens:    u32,
59    pub max_output_tokens:   u32,
60    pub self_correct_rounds: u32,
61}
62
63impl Default for Policy {
64    fn default() -> Self {
65        Self {
66            max_iters:           50,
67            max_input_tokens:    150_000,
68            max_output_tokens:   8_000,
69            self_correct_rounds: 3,
70        }
71    }
72}
73
74/// The model-visible state of an in-progress agent run.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct Context {
77    pub system:   Vec<Block>,
78    pub guides:   Vec<Block>,
79    pub history:  Vec<Turn>,
80    pub task:     Task,
81    pub policy:   Policy,
82    pub metadata: BTreeMap<String, serde_json::Value>,
83    /// Tools the agent may call this turn. Model adapters translate these to
84    /// the provider's tool-calling format (OpenAI `tools`, Anthropic `tools`, …).
85    pub tools:    Vec<crate::ToolSchema>,
86}
87
88impl Context {
89    pub fn new(task: Task) -> Self {
90        Self {
91            system:   Vec::new(),
92            guides:   Vec::new(),
93            history:  Vec::new(),
94            task,
95            policy:   Policy::default(),
96            metadata: BTreeMap::new(),
97            tools:    Vec::new(),
98        }
99    }
100
101    /// Append a model turn to the history. Captures reasoning content so it
102    /// can be echoed back on subsequent calls (required by DeepSeek thinking
103    /// mode and Anthropic thinking blocks).
104    pub fn push_model_output(&mut self, out: &ModelOutput) {
105        let mut blocks = Vec::new();
106        if let Some(r) = &out.reasoning
107            && !r.is_empty()
108        {
109            blocks.push(Block::Reasoning(r.clone()));
110        }
111        if let Some(t) = &out.text
112            && !t.is_empty()
113        {
114            blocks.push(Block::Text(t.clone()));
115        }
116        for c in &out.tool_calls {
117            blocks.push(Block::ToolCall {
118                call_id: c.id.clone(),
119                name:    c.name.clone(),
120                args:    c.args.clone(),
121            });
122        }
123        self.history.push(Turn { role: TurnRole::Assistant, blocks });
124    }
125
126    /// Append feedback signals as a tool-role turn.
127    pub fn push_feedback(&mut self, signals: Vec<Signal>) {
128        self.history.push(Turn {
129            role:   TurnRole::Tool,
130            blocks: vec![Block::Feedback(signals)],
131        });
132    }
133}
134
135/// One action the agent has asked to take, paired with the originating tool call.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Action {
138    pub tool:    String,
139    pub call_id: String,
140    pub args:    serde_json::Value,
141}