// // AgentStep.swift // Zyquo Agent // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // One iteration of the plan→act→observe→reflect loop: the assistant's thought // and text, the tool call(s) it issued, and the observed result(s). Steps are // the unit the UI renders as cards, the Transcript persists, and the // MemoryManager compacts. // import Foundation /// Lifecycle of a single step. enum AgentStepStatus: String, Codable, Sendable { /// Model output still streaming in. case streaming /// Tool call held at the policy gate, waiting for the user. case awaitingApproval /// Tool call(s) executing. case executing case completed case failed /// User denied the action or cancelled mid-step. case cancelled } /// One executed (or in-flight) tool call inside a step, with its observation. struct AgentToolInvocation: Codable, Identifiable, Sendable { var id: String { call.id } var call: ToolCall var result: ToolResult? /// Exit code for process-backed tools (bash, osascript). var exitCode: Int32? /// How the policy gate resolved this action. var policyDecision: PolicyDecisionRecord? var startedAt: Date? var finishedAt: Date? } /// Snapshot of the gate's ruling on an action, kept for the step card and audit. struct PolicyDecisionRecord: Codable, Sendable { enum Ruling: String, Codable, Sendable { case autoAllowed case approvedByUser case editedAndApproved case denied } var ruling: Ruling var riskLabel: String? /// Explanation shown on the approval card. var rationale: String? } /// One iteration of the agent loop. struct AgentStep: Codable, Identifiable, Sendable { var id: UUID = UUID() /// 1-based position in the run. var index: Int var status: AgentStepStatus = .streaming /// Reasoning-model thinking (collapsible in UI); nil for non-reasoning models. var thinking: String? /// The assistant's visible text for this turn (thought line and/or final answer). var text: String var toolInvocations: [AgentToolInvocation] = [] var startedAt: Date = Date() var finishedAt: Date? /// Tokens consumed by this step's model turn (for LoopGuard budgets). var inputTokens: Int? var outputTokens: Int? /// True when the model produced a final answer (no tool calls) — the loop's /// termination signal. var isFinal: Bool { toolInvocations.isEmpty && status == .completed } }