Skip to main content

ToolSpec

Struct ToolSpec 

Source
pub struct ToolSpec {
    pub name: String,
    pub version: String,
    pub side_effect: String,
    pub risk_level: String,
    pub idempotency_required: bool,
    pub approval_required: bool,
    pub sandbox_required: bool,
    pub sandbox_executor: String,
    pub sandbox_policy: State,
    pub input_schema: Option<Value>,
    pub output_schema: Option<Value>,
    pub func: ToolFunc,
}

Fields§

§name: String§version: String§side_effect: String§risk_level: String§idempotency_required: bool§approval_required: bool§sandbox_required: bool§sandbox_executor: String§sandbox_policy: State§input_schema: Option<Value>§output_schema: Option<Value>§func: ToolFunc

Implementations§

Source§

impl ToolSpec

Source

pub fn new(name: &str, func: ToolFunc) -> Self

Examples found in repository?
examples/three_minute_demo.rs (lines 23-31)
20fn main() -> agentledger::Result<()> {
21    let mut runtime = Runtime::new();
22    runtime.register_tool(
23        ToolSpec::new(
24            "ticket.create",
25            Box::new(|args| {
26                Ok(Value::Object(state(&[
27                    ("external_id", "TICKET-1".into()),
28                    ("title", args["title"].clone()),
29                ])))
30            }),
31        )
32        .side_effect("external_write")
33        .idempotency_required(true)
34        .input_schema(Value::Object(state(&[
35            ("type", "object".into()),
36            (
37                "required",
38                Value::Array(vec![Value::String("title".to_string())]),
39            ),
40        ]))),
41    );
42    let (run_id, _) = runtime.create_run(State::new());
43
44    let first_claim = runtime.store.claim_step("worker-before-crash", &run_id, 60.0)?;
45    let first_ctx = AgentContext {
46        run_id: run_id.clone(),
47        session_id: first_claim.session_id.clone(),
48        step_id: first_claim.step_id.clone(),
49        agent_role: "SupportAgent".to_string(),
50        lease_token: first_claim.lease_token.clone(),
51        attempt: first_claim.attempt,
52        state_version: first_claim.state_version,
53        pending_patch: State::new(),
54    };
55    let ticket = runtime.call_tool(
56        &first_ctx,
57        "ticket.create",
58        state(&[
59            ("title", "Investigate failed payment".into()),
60            ("_logical_operation", "open-payment-ticket".into()),
61        ]),
62    )?;
63    runtime.store.mark_retry(
64        &run_id,
65        &first_ctx.step_id,
66        "RetryableAgentError",
67        "after external ticket create, before state commit",
68    );
69
70    let second_claim = runtime.store.claim_step("worker-after-restart", &run_id, 60.0)?;
71    let mut second_ctx = AgentContext {
72        run_id: run_id.clone(),
73        session_id: second_claim.session_id.clone(),
74        step_id: second_claim.step_id.clone(),
75        agent_role: "SupportAgent".to_string(),
76        lease_token: second_claim.lease_token.clone(),
77        attempt: second_claim.attempt,
78        state_version: second_claim.state_version,
79        pending_patch: State::new(),
80    };
81    let replayed_ticket = runtime.call_tool(
82        &second_ctx,
83        "ticket.create",
84        state(&[
85            ("title", "Investigate failed payment".into()),
86            ("_logical_operation", "open-payment-ticket".into()),
87        ]),
88    )?;
89    second_ctx.write_state("ticket", replayed_ticket);
90    second_ctx.write_state("recovered", true.into());
91    runtime.store.commit_state_patch(
92        &run_id,
93        &second_ctx.step_id,
94        &second_ctx.lease_token,
95        second_ctx.state_version,
96        second_ctx.pending_patch,
97    )?;
98
99    let summary = replay(&runtime.store, &run_id)?;
100    let ledger = runtime.store.ledger(&run_id);
101    let final_state = runtime.store.final_state(&run_id)?;
102    println!(
103        "{{\n  \"run_id\": \"{}\",\n  \"first_attempt_ok\": false,\n  \"second_attempt_ok\": true,\n  \"external_ticket_count\": 1,\n  \"actual_tool_executions\": 1,\n  \"tool_ledger_count\": {},\n  \"tool_ledger_status\": \"{}\",\n  \"first_ticket_external_id\": \"{}\",\n  \"final_state_recovered\": {},\n  \"replay\": {{\n    \"safe\": {},\n    \"event_count\": {},\n    \"tool_call_count\": {}\n  }}\n}}",
104        run_id,
105        ledger.len(),
106        ledger.first().map(|row| row.status.as_str()).unwrap_or(""),
107        get_string(&ticket, "external_id"),
108        final_state.get("recovered") == Some(&Value::Bool(true)),
109        summary.replay_safe,
110        summary.event_count,
111        summary.tool_call_count
112    );
113    Ok(())
114}
Source

pub fn side_effect(self, side_effect: &str) -> Self

Examples found in repository?
examples/three_minute_demo.rs (line 32)
20fn main() -> agentledger::Result<()> {
21    let mut runtime = Runtime::new();
22    runtime.register_tool(
23        ToolSpec::new(
24            "ticket.create",
25            Box::new(|args| {
26                Ok(Value::Object(state(&[
27                    ("external_id", "TICKET-1".into()),
28                    ("title", args["title"].clone()),
29                ])))
30            }),
31        )
32        .side_effect("external_write")
33        .idempotency_required(true)
34        .input_schema(Value::Object(state(&[
35            ("type", "object".into()),
36            (
37                "required",
38                Value::Array(vec![Value::String("title".to_string())]),
39            ),
40        ]))),
41    );
42    let (run_id, _) = runtime.create_run(State::new());
43
44    let first_claim = runtime.store.claim_step("worker-before-crash", &run_id, 60.0)?;
45    let first_ctx = AgentContext {
46        run_id: run_id.clone(),
47        session_id: first_claim.session_id.clone(),
48        step_id: first_claim.step_id.clone(),
49        agent_role: "SupportAgent".to_string(),
50        lease_token: first_claim.lease_token.clone(),
51        attempt: first_claim.attempt,
52        state_version: first_claim.state_version,
53        pending_patch: State::new(),
54    };
55    let ticket = runtime.call_tool(
56        &first_ctx,
57        "ticket.create",
58        state(&[
59            ("title", "Investigate failed payment".into()),
60            ("_logical_operation", "open-payment-ticket".into()),
61        ]),
62    )?;
63    runtime.store.mark_retry(
64        &run_id,
65        &first_ctx.step_id,
66        "RetryableAgentError",
67        "after external ticket create, before state commit",
68    );
69
70    let second_claim = runtime.store.claim_step("worker-after-restart", &run_id, 60.0)?;
71    let mut second_ctx = AgentContext {
72        run_id: run_id.clone(),
73        session_id: second_claim.session_id.clone(),
74        step_id: second_claim.step_id.clone(),
75        agent_role: "SupportAgent".to_string(),
76        lease_token: second_claim.lease_token.clone(),
77        attempt: second_claim.attempt,
78        state_version: second_claim.state_version,
79        pending_patch: State::new(),
80    };
81    let replayed_ticket = runtime.call_tool(
82        &second_ctx,
83        "ticket.create",
84        state(&[
85            ("title", "Investigate failed payment".into()),
86            ("_logical_operation", "open-payment-ticket".into()),
87        ]),
88    )?;
89    second_ctx.write_state("ticket", replayed_ticket);
90    second_ctx.write_state("recovered", true.into());
91    runtime.store.commit_state_patch(
92        &run_id,
93        &second_ctx.step_id,
94        &second_ctx.lease_token,
95        second_ctx.state_version,
96        second_ctx.pending_patch,
97    )?;
98
99    let summary = replay(&runtime.store, &run_id)?;
100    let ledger = runtime.store.ledger(&run_id);
101    let final_state = runtime.store.final_state(&run_id)?;
102    println!(
103        "{{\n  \"run_id\": \"{}\",\n  \"first_attempt_ok\": false,\n  \"second_attempt_ok\": true,\n  \"external_ticket_count\": 1,\n  \"actual_tool_executions\": 1,\n  \"tool_ledger_count\": {},\n  \"tool_ledger_status\": \"{}\",\n  \"first_ticket_external_id\": \"{}\",\n  \"final_state_recovered\": {},\n  \"replay\": {{\n    \"safe\": {},\n    \"event_count\": {},\n    \"tool_call_count\": {}\n  }}\n}}",
104        run_id,
105        ledger.len(),
106        ledger.first().map(|row| row.status.as_str()).unwrap_or(""),
107        get_string(&ticket, "external_id"),
108        final_state.get("recovered") == Some(&Value::Bool(true)),
109        summary.replay_safe,
110        summary.event_count,
111        summary.tool_call_count
112    );
113    Ok(())
114}
Source

pub fn risk_level(self, risk_level: &str) -> Self

Source

pub fn idempotency_required(self, required: bool) -> Self

Examples found in repository?
examples/three_minute_demo.rs (line 33)
20fn main() -> agentledger::Result<()> {
21    let mut runtime = Runtime::new();
22    runtime.register_tool(
23        ToolSpec::new(
24            "ticket.create",
25            Box::new(|args| {
26                Ok(Value::Object(state(&[
27                    ("external_id", "TICKET-1".into()),
28                    ("title", args["title"].clone()),
29                ])))
30            }),
31        )
32        .side_effect("external_write")
33        .idempotency_required(true)
34        .input_schema(Value::Object(state(&[
35            ("type", "object".into()),
36            (
37                "required",
38                Value::Array(vec![Value::String("title".to_string())]),
39            ),
40        ]))),
41    );
42    let (run_id, _) = runtime.create_run(State::new());
43
44    let first_claim = runtime.store.claim_step("worker-before-crash", &run_id, 60.0)?;
45    let first_ctx = AgentContext {
46        run_id: run_id.clone(),
47        session_id: first_claim.session_id.clone(),
48        step_id: first_claim.step_id.clone(),
49        agent_role: "SupportAgent".to_string(),
50        lease_token: first_claim.lease_token.clone(),
51        attempt: first_claim.attempt,
52        state_version: first_claim.state_version,
53        pending_patch: State::new(),
54    };
55    let ticket = runtime.call_tool(
56        &first_ctx,
57        "ticket.create",
58        state(&[
59            ("title", "Investigate failed payment".into()),
60            ("_logical_operation", "open-payment-ticket".into()),
61        ]),
62    )?;
63    runtime.store.mark_retry(
64        &run_id,
65        &first_ctx.step_id,
66        "RetryableAgentError",
67        "after external ticket create, before state commit",
68    );
69
70    let second_claim = runtime.store.claim_step("worker-after-restart", &run_id, 60.0)?;
71    let mut second_ctx = AgentContext {
72        run_id: run_id.clone(),
73        session_id: second_claim.session_id.clone(),
74        step_id: second_claim.step_id.clone(),
75        agent_role: "SupportAgent".to_string(),
76        lease_token: second_claim.lease_token.clone(),
77        attempt: second_claim.attempt,
78        state_version: second_claim.state_version,
79        pending_patch: State::new(),
80    };
81    let replayed_ticket = runtime.call_tool(
82        &second_ctx,
83        "ticket.create",
84        state(&[
85            ("title", "Investigate failed payment".into()),
86            ("_logical_operation", "open-payment-ticket".into()),
87        ]),
88    )?;
89    second_ctx.write_state("ticket", replayed_ticket);
90    second_ctx.write_state("recovered", true.into());
91    runtime.store.commit_state_patch(
92        &run_id,
93        &second_ctx.step_id,
94        &second_ctx.lease_token,
95        second_ctx.state_version,
96        second_ctx.pending_patch,
97    )?;
98
99    let summary = replay(&runtime.store, &run_id)?;
100    let ledger = runtime.store.ledger(&run_id);
101    let final_state = runtime.store.final_state(&run_id)?;
102    println!(
103        "{{\n  \"run_id\": \"{}\",\n  \"first_attempt_ok\": false,\n  \"second_attempt_ok\": true,\n  \"external_ticket_count\": 1,\n  \"actual_tool_executions\": 1,\n  \"tool_ledger_count\": {},\n  \"tool_ledger_status\": \"{}\",\n  \"first_ticket_external_id\": \"{}\",\n  \"final_state_recovered\": {},\n  \"replay\": {{\n    \"safe\": {},\n    \"event_count\": {},\n    \"tool_call_count\": {}\n  }}\n}}",
104        run_id,
105        ledger.len(),
106        ledger.first().map(|row| row.status.as_str()).unwrap_or(""),
107        get_string(&ticket, "external_id"),
108        final_state.get("recovered") == Some(&Value::Bool(true)),
109        summary.replay_safe,
110        summary.event_count,
111        summary.tool_call_count
112    );
113    Ok(())
114}
Source

pub fn approval_required(self, required: bool) -> Self

Source

pub fn sandbox_required(self, required: bool) -> Self

Source

pub fn sandbox_executor(self, executor: &str) -> Self

Source

pub fn sandbox_policy(self, policy: State) -> Self

Source

pub fn input_schema(self, schema: Value) -> Self

Examples found in repository?
examples/three_minute_demo.rs (lines 34-40)
20fn main() -> agentledger::Result<()> {
21    let mut runtime = Runtime::new();
22    runtime.register_tool(
23        ToolSpec::new(
24            "ticket.create",
25            Box::new(|args| {
26                Ok(Value::Object(state(&[
27                    ("external_id", "TICKET-1".into()),
28                    ("title", args["title"].clone()),
29                ])))
30            }),
31        )
32        .side_effect("external_write")
33        .idempotency_required(true)
34        .input_schema(Value::Object(state(&[
35            ("type", "object".into()),
36            (
37                "required",
38                Value::Array(vec![Value::String("title".to_string())]),
39            ),
40        ]))),
41    );
42    let (run_id, _) = runtime.create_run(State::new());
43
44    let first_claim = runtime.store.claim_step("worker-before-crash", &run_id, 60.0)?;
45    let first_ctx = AgentContext {
46        run_id: run_id.clone(),
47        session_id: first_claim.session_id.clone(),
48        step_id: first_claim.step_id.clone(),
49        agent_role: "SupportAgent".to_string(),
50        lease_token: first_claim.lease_token.clone(),
51        attempt: first_claim.attempt,
52        state_version: first_claim.state_version,
53        pending_patch: State::new(),
54    };
55    let ticket = runtime.call_tool(
56        &first_ctx,
57        "ticket.create",
58        state(&[
59            ("title", "Investigate failed payment".into()),
60            ("_logical_operation", "open-payment-ticket".into()),
61        ]),
62    )?;
63    runtime.store.mark_retry(
64        &run_id,
65        &first_ctx.step_id,
66        "RetryableAgentError",
67        "after external ticket create, before state commit",
68    );
69
70    let second_claim = runtime.store.claim_step("worker-after-restart", &run_id, 60.0)?;
71    let mut second_ctx = AgentContext {
72        run_id: run_id.clone(),
73        session_id: second_claim.session_id.clone(),
74        step_id: second_claim.step_id.clone(),
75        agent_role: "SupportAgent".to_string(),
76        lease_token: second_claim.lease_token.clone(),
77        attempt: second_claim.attempt,
78        state_version: second_claim.state_version,
79        pending_patch: State::new(),
80    };
81    let replayed_ticket = runtime.call_tool(
82        &second_ctx,
83        "ticket.create",
84        state(&[
85            ("title", "Investigate failed payment".into()),
86            ("_logical_operation", "open-payment-ticket".into()),
87        ]),
88    )?;
89    second_ctx.write_state("ticket", replayed_ticket);
90    second_ctx.write_state("recovered", true.into());
91    runtime.store.commit_state_patch(
92        &run_id,
93        &second_ctx.step_id,
94        &second_ctx.lease_token,
95        second_ctx.state_version,
96        second_ctx.pending_patch,
97    )?;
98
99    let summary = replay(&runtime.store, &run_id)?;
100    let ledger = runtime.store.ledger(&run_id);
101    let final_state = runtime.store.final_state(&run_id)?;
102    println!(
103        "{{\n  \"run_id\": \"{}\",\n  \"first_attempt_ok\": false,\n  \"second_attempt_ok\": true,\n  \"external_ticket_count\": 1,\n  \"actual_tool_executions\": 1,\n  \"tool_ledger_count\": {},\n  \"tool_ledger_status\": \"{}\",\n  \"first_ticket_external_id\": \"{}\",\n  \"final_state_recovered\": {},\n  \"replay\": {{\n    \"safe\": {},\n    \"event_count\": {},\n    \"tool_call_count\": {}\n  }}\n}}",
104        run_id,
105        ledger.len(),
106        ledger.first().map(|row| row.status.as_str()).unwrap_or(""),
107        get_string(&ticket, "external_id"),
108        final_state.get("recovered") == Some(&Value::Bool(true)),
109        summary.replay_safe,
110        summary.event_count,
111        summary.tool_call_count
112    );
113    Ok(())
114}
Source

pub fn output_schema(self, schema: Value) -> Self

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.