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

Function record_turn

atomic-agent/src/record/mod.rs:136–437  ·  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 mut 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 // `status()` reads current_view, while `record()` writes to session.view_name.
152 // Align the read-only handle before the first status check; this keeps the
153 // no-lock fast path and prevents direct callers from seeing false EmptyTurn.
154 repo.set_current_view_in_memory(&options.session.view_name);
155
156 // Step 2: Status — find out what the agent changed.
157 // Include untracked files because agent turns commonly create new source,
158 // config, and test files. Those must be auto-added before recording so the
159 // turn produces an Atomic change with provenance instead of leaving files
160 // untracked in the working copy.
161 let status = repo
162 .status(atomic_repository::status::StatusOptions::fast().with_untracked(true))
163 .map_err(|e| AgentError::RecordFailed {
164 session_id: options.session.session_id.clone(),
165 turn_number: options.turn_number,
166 reason: format!("Failed to get repository status: {}", e),
167 })?;
168
169 // Check if there's anything to record at all
170 if status.is_clean() && status.untracked_count() == 0 {
171 return Err(AgentError::EmptyTurn {
172 session_id: options.session.session_id.clone(),
173 turn_number: options.turn_number,
174 });
175 }
176
177 // Step 3: Add — track any new files the agent created
178 // Agents create new files all the time (new modules, tests, configs).
179 // These show up as "untracked" in status. We add them before recording
180 // so they're included in the change.
181 //
182 // We filter out common large directories (node_modules, target, etc.)
183 // that agents may create as side effects (e.g., `npm install`). These
184 // would make the hook extremely slow and are never intended to be
185 // version-controlled. The .atomicignore file provides user-level control,
186 // but these defaults protect against the common case where no ignore
187 // file exists yet.
188 let untracked_paths: Vec<String> = status
189 .untracked()
190 .map(|e| e.path().to_string_lossy().to_string())
191 .filter(|p| !should_ignore_untracked(p))
192 .collect();
193

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