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

Function record_turn

atomic-agent/src/record/mod.rs:138–534  ·  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

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