feat: Add agent block#356
Conversation
Comprehensive spec for a new "agent" block type that supports: - External ACP-compatible agents (Claude Code, Gemini, Codex) - Internal Atuin agent using existing AI infrastructure - Streaming responses with state tracking - Tool call approval flow (confirm with always-allow opt-in) - Template integration for prompts and output access Includes Rust structs, execution flow, ACP client implementation, UI component requirements, and testing strategy.
Greptile SummaryAdds comprehensive spec for internal agent block that invokes Atuin's AI agent with templated prompts. Uses existing AI infrastructure (FSM, session driver, streaming, multi-provider) with new Issues found:
Important Files Changed
|
After deeper analysis of the codebase, significantly revised the spec: - Added "Existing Infrastructure to Reuse" section documenting FSM, session driver, tool system, streaming, and storage - Rewrote Internal Agent Implementation to use existing AISession instead of building new streaming/tool handling - Simplified to: create session → send prompt → wait for idle - Added specific changes needed to existing files (session.rs, fsm.rs, commands/ai.rs) rather than new implementations - Updated Implementation Order with phased approach and effort estimates reflecting reduced scope for Phase 1 Key insight: Internal agent is mostly wiring, not building.
- Replaced detailed ACP implementation with interface contract - ACP section now just documents what execute_acp_agent() must do - Updated Implementation Order to emphasize "START HERE" for Phase 1 - Marked Phase 2 and 3 as explicitly DEFERRED - Reorganized Open Questions by phase - Reduced spec complexity while preserving ACP extensibility The AgentSource enum ensures ACP slots in cleanly later: same output structure, same state machine, same streaming pattern.
Removed all ACP/deferred content. Spec is now: - 462 lines (down from 1273) - Internal agent only - Single implementation checklist - No phases, no deferrals Ready for implementation handoff.
|
@greptile review the spec again |
|
|
||
| #[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
| pub struct TokenUsage { | ||
| pub input_tokens: Option<u64>, | ||
| pub output_tokens: Option<u64>, | ||
| } |
There was a problem hiding this comment.
syntax: TokenUsage lacks #[ts(export)] and TS derive, won't be available in TypeScript
| #[derive(Debug, Clone, Serialize, Deserialize, Default)] | |
| pub struct TokenUsage { | |
| pub input_tokens: Option<u64>, | |
| pub output_tokens: Option<u64>, | |
| } | |
| #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] | |
| #[ts(export)] | |
| pub struct TokenUsage { | |
| pub input_tokens: Option<u64>, | |
| pub output_tokens: Option<u64>, | |
| } |
| .stdout(Some(chunk)) | ||
| .build() | ||
| ); | ||
| context.update_block_state::<AgentState, _>(|s| { |
There was a problem hiding this comment.
syntax: Missing .await on update_block_state
| context.update_block_state::<AgentState, _>(|s| { | |
| }).await; |
| let result = ai_agent_execute( | ||
| context.runbook_id, | ||
| prompt, | ||
| system_prompt, | ||
| self.model, | ||
| self.always_allow_tools, | ||
| self.timeout_seconds, | ||
| |chunk| { | ||
| context.send_output(StreamingBlockOutput::builder() | ||
| .block_id(block_id) | ||
| .stdout(Some(chunk)) | ||
| .build() | ||
| ); | ||
| context.update_block_state::<AgentState, _>(|s| { | ||
| s.status = AgentStatus::Streaming; | ||
| }); | ||
| }, |
There was a problem hiding this comment.
logic: Closure cannot be passed to ai_agent_execute - that's a Tauri command invoked from frontend, not a callable Rust function. Streaming should happen via Tauri event channel
The ai_agent_execute command at line 247 uses Channel<String> for on_chunk, which is for frontend->backend IPC. The closure here won't work. Instead, need to either:
- Make this a regular backend function (not Tauri command) that accepts a closure
- Or invoke the Tauri command from frontend and receive chunks via the channel
Based on the existing AI infrastructure, this should likely call a backend function directly within the runtime, not a Tauri command
| #[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct AgentState { | ||
| pub status: AgentStatus, |
There was a problem hiding this comment.
syntax: AgentState needs TS derive and #[ts(export)] to generate TypeScript types
| #[derive(Debug, Clone, Serialize, Deserialize, Default)] | |
| #[serde(rename_all = "camelCase")] | |
| pub struct AgentState { | |
| pub status: AgentStatus, | |
| #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] | |
| #[serde(rename_all = "camelCase")] | |
| #[ts(export)] | |
| pub struct AgentState { |
| #[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub enum AgentStatus { | ||
| #[default] |
There was a problem hiding this comment.
syntax: AgentStatus needs TS derive and #[ts(export)] to generate TypeScript types
| #[derive(Debug, Clone, Serialize, Deserialize, Default)] | |
| #[serde(rename_all = "camelCase")] | |
| pub enum AgentStatus { | |
| #[default] | |
| #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] | |
| #[serde(rename_all = "camelCase")] | |
| #[ts(export)] | |
| pub enum AgentStatus { |
Fixes: 1. Add TS derive and #[ts(export)] to AgentState, AgentStatus, TokenUsage 2. Add missing .await on async update_block_state calls 3. Fix architectural mismatch: changed from Tauri command with closure to regular backend function with mpsc channel for streaming The execute_agent_block function is now a regular async function (not a Tauri command) since it runs in the backend block executor and uses mpsc::Sender<String> instead of Tauri's Channel<T>.
Comprehensive spec for a new "agent" block type that supports:
Includes Rust structs, execution flow, ACP client implementation,
UI component requirements, and testing strategy.