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

Function record_turn

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

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

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
is_sandboxMethod · 0.80