MCPcopy Create free account
hub / github.com/atomicdotdev/atomic / record_turn

Function record_turn

atomic-agent/src/record/mod.rs:140–553  ·  view source on GitHub ↗

Record an agent turn as an Atomic change. This is the function that bridges the agent world into the VCS world. It builds a `ChangeHeader`, `Provenance`, and `SessionEnvelope`, then calls the repository's `record()` method to create a proper content-addressed, hashable, pushable Atomic change. # Arguments `repo_root` — Path to the repository root (where `.atomic/` lives). The repository is open

(
    repo_root: &Path,
    options: &TurnRecordOptions<'_>,
)

Source from the content-addressed store, hash-verified

138/// (no files changed since the last recorded state).
139/// Returns `AgentError::RecordFailed` if the repository record operation fails.
140pub fn record_turn(
141 repo_root: &Path,
142 options: &TurnRecordOptions<'_>,
143) -> AgentResult<TurnRecordOutcome> {
144 let mut manifest = scope::manifest(options)?;
145 // Step 1: Open the repository read-only for the initial status check.
146 // This can coexist with other readers. Wait for a transient incompatible
147 // writer before deciding whether work or untracked files exist.
148 let mut repo = atomic_repository::Repository::open_readonly_wait(
149 repo_root,
150 std::time::Duration::from_secs(10),
151 )
152 .map_err(|e| AgentError::RecordFailed {
153 session_id: options.session.session_id.clone(),
154 turn_number: options.turn_number,
155 reason: format!("Failed to open repository (readonly): {}", e),
156 })?;
157
158 // `status()` reads current_view, while `record()` writes to session.view_name.
159 // Align the read-only handle before the first status check; this keeps the
160 // no-lock fast path and prevents direct callers from seeing false EmptyTurn.
161 repo.set_current_view_in_memory(&options.session.view_name);
162
163 // Step 2: Status — find out what the agent changed.
164 // Include untracked files because agent turns commonly create new source,
165 // config, and test files. Those must be auto-added before recording so the
166 // turn produces an Atomic change with provenance instead of leaving files
167 // untracked in the working copy.
168 let status = repo
169 .status(atomic_repository::status::StatusOptions::fast().with_untracked(true))
170 .map_err(|e| AgentError::RecordFailed {
171 session_id: options.session.session_id.clone(),
172 turn_number: options.turn_number,
173 reason: format!("Failed to get repository status: {}", e),
174 })?;
175
176 // Restart-proof the deletion scope. The plugin's ownership claims live in
177 // its process memory; a plugin restart mid-session (e.g. after an
178 // ambiguity lockout) silently drops every claim, and deletions of files
179 // this session recorded earlier then sit unrecorded forever — no later
180 // manifest will ever mention them again. Those deletions are still
181 // attributable from the persisted session state: `files_touched` holds
182 // exactly the paths this session's own changes introduced or modified.
183 // Augment the manifest with their Deleted status entries so a lost claim
184 // cannot strand a deletion. Files the session never recorded stay
185 // unclaimed, keeping foreign edits out of scope.
186 if let Some(files) = manifest.as_mut() {
187 let touched: std::collections::HashSet<&str> = options
188 .session
189 .files_touched
190 .iter()
191 .map(String::as_str)
192 .collect();
193 for entry in status.entries() {
194 if entry.status() != atomic_repository::status::FileStatus::Deleted {
195 continue;
196 }
197 let path = entry.path().to_string_lossy().to_string();

Calls 15

validateFunction · 0.85
filterFunction · 0.85
should_ignore_untrackedFunction · 0.85
build_turn_headerFunction · 0.85
build_turn_provenanceFunction · 0.85
build_turn_messageFunction · 0.85
build_turn_envelopeFunction · 0.85
resolve_turn_signerFunction · 0.85
build_unhashed_turn_dataFunction · 0.85
attach_unhashedFunction · 0.85
with_untrackedMethod · 0.80