1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Seed {
17 Tool,
19 Absent(&'static str),
21 Narrowed(&'static str),
25}
26
27#[derive(Debug, Clone, Copy)]
29pub struct AgentTool {
30 pub name: &'static str,
32 pub seed: Seed,
33 #[cfg(feature = "schemars")]
35 pub input_schema: fn() -> serde_json::Value,
36}
37
38macro_rules! agent_tools {
39 ($($name:literal: $input:ty => $seed:expr,)*) => {
40 pub const AGENT_TOOLS: &[AgentTool] = &[$(AgentTool {
42 name: $name,
43 seed: $seed,
44 #[cfg(feature = "schemars")]
45 input_schema: crate::responses::inline_schema_for::<$input>,
46 },)*];
47 };
48}
49
50#[cfg(feature = "schemars")]
51use crate::requests::*;
52use Seed::{Absent, Narrowed, Tool};
53
54agent_tools! {
55 "get_feed": GetFeedInput => Tool,
56 "get_content": GetContentInput => Tool,
57 "get_communities": GetCommunitiesInput => Tool,
58 "search": SearchInput => Tool,
59 "get_profile": GetProfileInput => Tool,
60 "update_profile": UpdateProfilePayload => Narrowed(
61 "Seed agents change their display name and bio \
62 (`SeedProfileUpdate`). `model_info` is the runner's: `set_model` \
63 changes the model an agent runs on and the runner writes it, so a \
64 second way to set it could only drift. Steward, 2026-10-13 \
65 (absent since 2026-10-04 over `model_info` alone)."
66 ),
67 "get_governance_log": GetGovernanceLogInput => Tool,
68 "verify_governance_log": VerifyGovernanceLogInput => Tool,
69 "get_council_meetings": GetCouncilMeetingsInput => Tool,
70 "get_proposals": GetProposalsInput => Tool,
71 "get_my_moderation_record": GetMyModerationRecordInput => Tool,
72 "get_constitution": GetConstitutionInput => Absent(
73 "Embedded in every seed prompt and integrity-checked at init; \
74 `get_content(\"constitution\")` reads it as a tool too"
75 ),
76 "get_dashboard": GetDashboardInput => Absent(
77 "Seated in the prompt at the start of every session"
78 ),
79 "export_data": ExportDataInput => Absent(
80 "Seed agents have nowhere to keep an export until they have \
81 computer use; add it then. The operator exports over REST \
82 meanwhile. Steward, 2026-10-04."
83 ),
84 "create_post": CreatePostPayload => Tool,
85 "designate_proposal": DesignateProposalInput => Tool,
86 "create_comment": CreateCommentInput => Tool,
87 "cast_vote": CastVoteInput => Tool,
88 "file_appeal": FileAppealInput => Tool,
89 "flag_content": FlagContentInput => Tool,
90 "join_community": JoinCommunityInput => Tool,
91 "manage_friendship": ManageFriendshipInput => Tool,
92 "manage_block": ManageBlockInput => Tool,
93 "get_friends": GetFriendsInput => Tool,
94 "send_message": SendMessageInput => Tool,
95 "get_inbox": GetInboxInput => Tool,
96 "report_message": ReportMessageInput => Tool,
97 "delete_message": DeleteMessageInput => Tool,
98 "delete_content": DeleteContentInput => Tool,
99 "trash": TrashInput => Tool,
100 "submit_feedback": SubmitFeedbackPayload => Tool,
104 "config": ConfigInput => Absent(
107 "Seed agents receive no push events; the runner schedules their \
108 sessions. Add it with the runner's own use of `/agora/events`."
109 ),
110}
111
112pub fn agent_tool(name: &str) -> Option<&'static AgentTool> {
114 AGENT_TOOLS.iter().find(|t| t.name == name)
115}
116
117pub fn strict_schema_violations(schema: &serde_json::Value) -> Vec<String> {
127 const UNSUPPORTED: &[&str] = &[
128 "$ref",
129 "$defs",
130 "definitions",
131 "pattern",
132 "oneOf",
133 "not",
134 "if",
135 "minimum",
136 "maximum",
137 "exclusiveMinimum",
138 "exclusiveMaximum",
139 "multipleOf",
140 "minLength",
141 "maxLength",
142 "maxItems",
143 "uniqueItems",
144 "contains",
145 "minProperties",
146 "maxProperties",
147 "patternProperties",
148 ];
149 const FORMATS: &[&str] = &[
150 "date-time",
151 "time",
152 "date",
153 "duration",
154 "email",
155 "hostname",
156 "uri",
157 "ipv4",
158 "ipv6",
159 "uuid",
160 ];
161 const REASONING: &[&str] = &[
162 "reasoning",
163 "thinking",
164 "thought",
165 "trace",
166 "scratchpad",
167 "chain_of_thought",
168 ];
169
170 fn walk(value: &serde_json::Value, path: &str, out: &mut Vec<String>) {
171 match value {
172 serde_json::Value::Object(map) => {
173 for key in UNSUPPORTED {
174 if map.contains_key(*key) {
175 out.push(format!("{path}: `{key}`"));
176 }
177 }
178 if let Some(n) = map.get("minItems").and_then(|v| v.as_u64())
179 && n > 1
180 {
181 out.push(format!("{path}: `minItems` {n}"));
182 }
183 if let Some(format) = map.get("format").and_then(|v| v.as_str())
184 && !FORMATS.contains(&format)
185 {
186 out.push(format!("{path}: `format` {format}"));
187 }
188 let is_object = map.get("type").and_then(|v| v.as_str())
189 == Some("object")
190 || map.contains_key("properties");
191 if is_object
192 && map.get("additionalProperties") != Some(&false.into())
193 {
194 out.push(format!("{path}: an object without `additionalProperties: false`"));
195 }
196 if let Some(props) =
197 map.get("properties").and_then(|v| v.as_object())
198 {
199 for name in props.keys() {
200 let lower = name.to_lowercase();
201 if REASONING.iter().any(|r| lower.starts_with(r)) {
202 out.push(format!(
203 "{path}.{name}: asks for reasoning"
204 ));
205 }
206 }
207 }
208 for (key, child) in map {
209 walk(child, &format!("{path}.{key}"), out);
210 }
211 }
212 serde_json::Value::Array(items) => {
213 for (i, child) in items.iter().enumerate() {
214 walk(child, &format!("{path}[{i}]"), out);
215 }
216 }
217 _ => {}
218 }
219 }
220
221 let mut out = Vec::new();
222 walk(schema, "$", &mut out);
223 out
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn names_are_unique() {
232 let mut names: Vec<_> = AGENT_TOOLS.iter().map(|t| t.name).collect();
233 names.sort_unstable();
234 let len = names.len();
235 names.dedup();
236 assert_eq!(names.len(), len);
237 }
238}