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

Function record_turn

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

134/// (no files changed since the last recorded state).
135/// Returns `AgentError::RecordFailed` if the repository record operation fails.
136pub fn record_turn(
137 repo_root: &Path,
138 options: &TurnRecordOptions<'_>,
139) -> AgentResult<TurnRecordOutcome> {
140 // Step 1: Open the repository read-only for the initial status check.
141 // This avoids blocking on the redb write lock — we only need read access
142 // to decide whether there's work to do and which files are untracked.
143 let repo = atomic_repository::Repository::open_readonly(repo_root).map_err(|e| {
144 AgentError::RecordFailed {
145 session_id: options.session.session_id.clone(),
146 turn_number: options.turn_number,
147 reason: format!("Failed to open repository (readonly): {}", e),
148 }
149 })?;
150
151 // Step 2: Status — find out what the agent changed.
152 // Include untracked files because agent turns commonly create new source,
153 // config, and test files. Those must be auto-added before recording so the
154 // turn produces an Atomic change with provenance instead of leaving files
155 // untracked in the working copy.
156 let status = repo
157 .status(atomic_repository::status::StatusOptions::fast().with_untracked(true))
158 .map_err(|e| AgentError::RecordFailed {
159 session_id: options.session.session_id.clone(),
160 turn_number: options.turn_number,
161 reason: format!("Failed to get repository status: {}", e),
162 })?;
163
164 // Check if there's anything to record at all
165 if status.is_clean() && status.untracked_count() == 0 {
166 return Err(AgentError::EmptyTurn {
167 session_id: options.session.session_id.clone(),
168 turn_number: options.turn_number,
169 });
170 }
171
172 // Step 3: Add — track any new files the agent created
173 // Agents create new files all the time (new modules, tests, configs).
174 // These show up as "untracked" in status. We add them before recording
175 // so they're included in the change.
176 //
177 // We filter out common large directories (node_modules, target, etc.)
178 // that agents may create as side effects (e.g., `npm install`). These
179 // would make the hook extremely slow and are never intended to be
180 // version-controlled. The .atomicignore file provides user-level control,
181 // but these defaults protect against the common case where no ignore
182 // file exists yet.
183 let untracked_paths: Vec<String> = status
184 .untracked()
185 .map(|e| e.path().to_string_lossy().to_string())
186 .filter(|p| !should_ignore_untracked(p))
187 .collect();
188
189 // Drop the read-only handle before opening with write access.
190 // This ensures we don't hold two database handles simultaneously.
191 drop(repo);
192
193 // Re-open with write capability for add + record. `open_existing()`

Callers 2

handle_turn_endMethod · 0.85

Calls 15

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
untracked_countMethod · 0.80
untrackedMethod · 0.80
add_batchMethod · 0.80
entriesMethod · 0.80

Tested by 1