mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-12 14:19:00 +01:00
feat(observability): span the unnamed gaps in run preparation (#20396)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Before a task reaches the agent, the server prepares the run: it reads the task, the workspace and the configuration, and then hands the run to the native controller > - With an OTLP endpoint, each preparation step emits a `heartbeat.prepare.<phase>` or `native.controller.<phase>` span > - On a hosted test deployment, 0.3 to 0.5 s of each `task.prepare` sits between those spans, and about 0.5 s more sits between `execution.dispatch_admission` and `native.coordinator.claim` > - Nobody can say which step is slow in that time, so latency work in this area is guesswork > - This pull request adds spans around the existing awaited work in those gaps, with the existing timing helpers and naming conventions > - The benefit is that a trace of a slow preparation now names the step that used the time ## Linked Issues or Issue Description **What existing behavior does this improve?** The OpenTelemetry trace of task preparation. Today it has unnamed gaps between the named preparation spans. **Subsystem affected** Server: heartbeat run preparation and the native controller (`server/src/services/heartbeat.ts`, `server/src/services/native-runtime/native-session-executor.ts`). **Current behavior** On a hosted test deployment (8 runs per group), `task.prepare` takes 2.6 to 3.4 s. Between `heartbeat.prepare.runtime_state` and `heartbeat.prepare.run_identity`, about 0.25 s has no span. Between `execution.dispatch_admission` and `native.coordinator.claim`, about 0.5 s has no span, and in one pair of runs it was 1.4 s. Nobody can tell which step used that time. **Proposed behavior** Spans name the existing awaited reads and writes in those gaps. The change adds no reads and records no content. The operator endpoint gate and the no-operation behaviour stay. **Reason and benefit** Latency work in this area needs a measurement. With these spans, a trace of a slow preparation names the step that used the time, so a fix can be confirmed or rejected after deployment. **Breaking changes** None. The change adds span names to the closed list. It changes no API, schema or behaviour. **Additional context** The server uses postgres.js, which the auto-instrumentation does not cover, so database spans do not fill these gaps. ## What Changed - `server/src/services/execution-preparation-timing.ts`: add the new closed phase names. - `server/src/services/heartbeat.ts`: add `heartbeat.prepare.*` spans for `chat_retry_admission`, `workspace_selection`, `dependency_readiness`, `issue_checkout`, `instance_settings`, `workspace_context`, `project_context`, `plan_wake_routing`, `routine_environment`, `responsible_user` and `execution_start`. The re-reads of the issue after a checkout use the existing `issue_context` name. - `server/src/services/native-runtime/native-session-executor.ts`: add `native.controller.*` spans for `attachment_staging`, `warm_restore`, `span_persistence` (persistence of the preparation spans to the run log) and `state_migration` before the coordinator claim. - `doc/observability.md`: list the new spans. - `server/src/__tests__/heartbeat-preparation-gap-spans.test.ts`: run a real heartbeat through native preparation with an in-memory span exporter. Check that each new span is emitted and that the new preparation spans are children of `task.execution_attempt`. ## Verification - `pnpm --filter @paperclipai/server typecheck` - `npx vitest run src/__tests__/heartbeat-preparation-gap-spans.test.ts` (fails without the source change, passes with it) - `npx vitest run src/services/heartbeat/preparation-timing-context.test.ts src/services/execution-preparation-timing.test.ts src/services/native-runtime/native-session-executor.test.ts src/__tests__/heartbeat-context-compaction-execution.test.ts` ## Risks - Low risk. The change only wraps existing awaited calls. Values and errors pass through the timing helper unchanged, and a checkout conflict still reaches the same `catch`. - Phases of 50 ms or more inside `heartbeat.prepare.*` are also written to the run log, as for the existing phases. This adds a few run-log rows on slow runs. - Without an OTLP endpoint, the helpers keep their current no-operation behaviour. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, test runs) and extended thinking. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
09241442ea
commit
d95fdd8d79
5 files changed
+234
-73
No files matched your search
@@ -7,6 +7,9 @@ import { isSpanContextValid, ROOT_CONTEXT, SpanStatusCode, trace, type Context }
|
||||
type PreparationPhase = "environment_selection" | "execution_configuration" | "session"
|
||||
| "agent_read" | "chat_completion" | "conversation" | "runtime_state" | "issue_context"
|
||||
| "run_identity" | "trust_preset" | "task_context"
|
||||
| "chat_retry_admission" | "workspace_selection" | "dependency_readiness" | "issue_checkout"
|
||||
| "instance_settings" | "workspace_context" | "project_context" | "plan_wake_routing"
|
||||
| "routine_environment" | "responsible_user" | "execution_start"
|
||||
| "workspace" | "github_environment" | "github_launchers" | "run_output"
|
||||
| "workspace_validation" | "runtime_services" | "instructions" | "runtime_selection"
|
||||
| "configuration_mentions" | "configuration_github_identity" | "configuration_secrets"
|
||||
@@ -34,7 +37,8 @@ type PreparationPhase = "environment_selection" | "execution_configuration" | "s
|
||||
| "github_access" | "wake_binding" | "backend_setup" | "connector_assignments"
|
||||
| "assigned_mcp" | "tool_authority" | "companion_selection" | "runner_binary"
|
||||
| "github_stop" | "coordinator_release" | "instruction_checkpoint"
|
||||
| "instruction_changes" | "warm_release" | "instruction_collection";
|
||||
| "instruction_changes" | "warm_release" | "instruction_collection"
|
||||
| "attachment_staging" | "warm_restore" | "span_persistence" | "state_migration";
|
||||
|
||||
export type ExecutionPhase =
|
||||
| "skills.inventory" | "skills.inventory.wait" | "skills.inventory.bundled" | "skills.inventory.releases"
|
||||
|
||||
@@ -2153,7 +2153,7 @@ export function heartbeatService(
|
||||
contextSnapshot: context,
|
||||
}));
|
||||
});
|
||||
const isFailedChatRunRetry = await authorizeFailedChatRetryExecution();
|
||||
const isFailedChatRunRetry = await timePreparation("chat_retry_admission", () => authorizeFailedChatRetryExecution());
|
||||
// Never adopt a chat-execution attestation supplied in a wake payload.
|
||||
// Reviewed chat turns rebuild it from the current durable owner below.
|
||||
delete context[PAPERCLIP_EXTERNAL_CHAT_EXECUTION_BOUND_KEY];
|
||||
@@ -2208,28 +2208,31 @@ export function heartbeatService(
|
||||
const taskKey = deriveTaskKeyWithHeartbeatFallback(context, null);
|
||||
const sessionCodec = getAdapterSessionCodec(agent.adapterType);
|
||||
const issueId = readNonEmptyString(context.issueId);
|
||||
const freshWorkspace = await readFreshWorkspaceReadiness(db, run);
|
||||
if (issueId && canApplyTaskWorkspaceSelectionAtAdmission({ admittedInput: parseObject(run.runnerProfileJson).nativeExecutionInput, restarting: Boolean(runOptions.nativeRestartRecovery), hasLeaseOwner: Boolean(runOptions.nativeLeaseOwner) })) {
|
||||
const changed = await executionWorkspacesSvc.applyPendingTaskWorkspaceSelection({ companyId: agent.companyId,
|
||||
issueId, runId: run.id, actor: { type: "agent", agentId: agent.id, companyId: agent.companyId, source: "agent_key" } });
|
||||
// A subtask an older release bound to its parent's workspace gets its own
|
||||
// here. A process-loss retry or a continuation that keeps the current
|
||||
// workspace still finishes the interrupted attempt there first.
|
||||
const released = run.processLossRetryCount > 0 || (freshWorkspace && freshWorkspace.mode !== "fresh_isolated_workspace") ? false
|
||||
: await executionWorkspacesSvc.releaseAncestorWorkspaceBindingAtAdmission({ companyId: agent.companyId, issueId, runId: run.id });
|
||||
if (changed || released) {
|
||||
delete context.resumeSessionParams;
|
||||
delete context.resumeSessionDisplayId;
|
||||
context.workspaceSelectionChanged = true;
|
||||
const freshWorkspace = await timePreparation("workspace_selection", async () => {
|
||||
const readiness = await readFreshWorkspaceReadiness(db, run);
|
||||
if (issueId && canApplyTaskWorkspaceSelectionAtAdmission({ admittedInput: parseObject(run.runnerProfileJson).nativeExecutionInput, restarting: Boolean(runOptions.nativeRestartRecovery), hasLeaseOwner: Boolean(runOptions.nativeLeaseOwner) })) {
|
||||
const changed = await executionWorkspacesSvc.applyPendingTaskWorkspaceSelection({ companyId: agent.companyId,
|
||||
issueId, runId: run.id, actor: { type: "agent", agentId: agent.id, companyId: agent.companyId, source: "agent_key" } });
|
||||
// A subtask an older release bound to its parent's workspace gets its own
|
||||
// here. A process-loss retry or a continuation that keeps the current
|
||||
// workspace still finishes the interrupted attempt there first.
|
||||
const released = run.processLossRetryCount > 0 || (readiness && readiness.mode !== "fresh_isolated_workspace") ? false
|
||||
: await executionWorkspacesSvc.releaseAncestorWorkspaceBindingAtAdmission({ companyId: agent.companyId, issueId, runId: run.id });
|
||||
if (changed || released) {
|
||||
delete context.resumeSessionParams;
|
||||
delete context.resumeSessionDisplayId;
|
||||
context.workspaceSelectionChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return readiness;
|
||||
});
|
||||
let issueContext = issueId
|
||||
? await timePreparation("issue_context", () => getIssueExecutionContext(agent.companyId, issueId))
|
||||
: null;
|
||||
const issueDependencyReadiness = issueId
|
||||
? await issuesSvc
|
||||
? await timePreparation("dependency_readiness", () => issuesSvc
|
||||
.listDependencyReadiness(agent.companyId, [issueId])
|
||||
.then((rows) => rows.get(issueId) ?? null)
|
||||
.then((rows) => rows.get(issueId) ?? null))
|
||||
: null;
|
||||
const dependencyResponse = issueId && issueContext?.status === "blocked"
|
||||
&& context.dependencyBlockedInteraction === true
|
||||
@@ -2247,14 +2250,17 @@ export function heartbeatService(
|
||||
// dispatch: an operator parking the issue after claim but before this
|
||||
// checkout must not be overwritten by the continuation. A todo task
|
||||
// that has never started has nothing parked, so it may start here.
|
||||
const startsUnstartedTask = issueContext.status === "todo" &&
|
||||
await isUnstartedTaskQuestionAnswer(db, { companyId: agent.companyId, issueId, interactionId: context.interactionId });
|
||||
await issuesSvc.checkout(
|
||||
issueId,
|
||||
agent.id,
|
||||
[...resolvedInteractionCheckoutExpectedStatuses(), ...(startsUnstartedTask ? ["todo"] : [])],
|
||||
run.id,
|
||||
);
|
||||
const issueStatus = issueContext.status;
|
||||
await timePreparation("issue_checkout", async () => {
|
||||
const startsUnstartedTask = issueStatus === "todo" &&
|
||||
await isUnstartedTaskQuestionAnswer(db, { companyId: agent.companyId, issueId, interactionId: context.interactionId });
|
||||
await issuesSvc.checkout(
|
||||
issueId,
|
||||
agent.id,
|
||||
[...resolvedInteractionCheckoutExpectedStatuses(), ...(startsUnstartedTask ? ["todo"] : [])],
|
||||
run.id,
|
||||
);
|
||||
});
|
||||
context[PAPERCLIP_HARNESS_CHECKOUT_KEY] = true;
|
||||
} catch (error) {
|
||||
if (!isCheckoutConflictError(error)) throw error;
|
||||
@@ -2269,7 +2275,7 @@ export function heartbeatService(
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
issueContext = await getIssueExecutionContext(agent.companyId, issueId);
|
||||
issueContext = await timePreparation("issue_context", () => getIssueExecutionContext(agent.companyId, issueId));
|
||||
}
|
||||
if (
|
||||
issueId &&
|
||||
@@ -2286,18 +2292,18 @@ export function heartbeatService(
|
||||
})
|
||||
) {
|
||||
try {
|
||||
await issuesSvc.checkout(
|
||||
await timePreparation("issue_checkout", () => issuesSvc.checkout(
|
||||
issueId,
|
||||
agent.id,
|
||||
typeof context.workspaceReconstructionActionId === "string" ? ["todo"] : ["todo", "backlog", "blocked"],
|
||||
run.id,
|
||||
);
|
||||
));
|
||||
context[PAPERCLIP_HARNESS_CHECKOUT_KEY] = true;
|
||||
} catch (error) {
|
||||
if (!isCheckoutConflictError(error)) throw error;
|
||||
context[PAPERCLIP_HARNESS_CHECKOUT_KEY] = false;
|
||||
}
|
||||
issueContext = await getIssueExecutionContext(agent.companyId, issueId);
|
||||
issueContext = await timePreparation("issue_context", () => getIssueExecutionContext(agent.companyId, issueId));
|
||||
}
|
||||
if (
|
||||
issueId &&
|
||||
@@ -2338,7 +2344,7 @@ export function heartbeatService(
|
||||
)
|
||||
: null;
|
||||
const experimentalInstanceSettings =
|
||||
await instanceSettings.getExperimental();
|
||||
await timePreparation("instance_settings", () => instanceSettings.getExperimental());
|
||||
const isolatedWorkspacesEnabled =
|
||||
experimentalInstanceSettings.enableIsolatedWorkspaces;
|
||||
// Inert on its own: the operator default only reaches the resolver when
|
||||
@@ -2349,16 +2355,18 @@ export function heartbeatService(
|
||||
experimentalInstanceSettings.enableIsolatedWorkspacesByDefault;
|
||||
const providerApiPlacement = resolveProviderApiPlacement({ run, agent });
|
||||
const externalProvider = heartbeatExternalProvider({ run, agent }) ?? providerApiPlacement?.provider;
|
||||
const [taskWorkspaceIntentRow] = issueId && !externalProvider ? await db.select({ intent: issues.workspaceSelection }).from(issues)
|
||||
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId))) : [];
|
||||
const [taskWorkspaceIntentRow] = issueId && !externalProvider ? await timePreparation("workspace_context", () => db.select({ intent: issues.workspaceSelection }).from(issues)
|
||||
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId)))) : [];
|
||||
const contextProjectId = readNonEmptyString(context.projectId);
|
||||
const boundSourceWorkspace = !externalProvider && issueContext?.executionWorkspaceId
|
||||
? await executionWorkspacesSvc.getStoredById(agent.companyId, issueContext.executionWorkspaceId) : null;
|
||||
const [selectedWorkspaceSource] = !externalProvider && !boundSourceWorkspace && issueContext?.projectWorkspaceId
|
||||
? await db.select({ projectId: projectWorkspaces.projectId }).from(projectWorkspaces)
|
||||
.where(and(eq(projectWorkspaces.id, issueContext.projectWorkspaceId), eq(projectWorkspaces.companyId, agent.companyId))) : [];
|
||||
const executionWorkspaceId = issueContext?.executionWorkspaceId;
|
||||
const boundSourceWorkspace = !externalProvider && executionWorkspaceId
|
||||
? await timePreparation("workspace_context", () => executionWorkspacesSvc.getStoredById(agent.companyId, executionWorkspaceId)) : null;
|
||||
const projectWorkspaceId = issueContext?.projectWorkspaceId;
|
||||
const [selectedWorkspaceSource] = !externalProvider && !boundSourceWorkspace && projectWorkspaceId
|
||||
? await timePreparation("workspace_context", () => db.select({ projectId: projectWorkspaces.projectId }).from(projectWorkspaces)
|
||||
.where(and(eq(projectWorkspaces.id, projectWorkspaceId), eq(projectWorkspaces.companyId, agent.companyId)))) : [];
|
||||
const loadProjectContext = async (projectId: string | null | undefined) => projectId
|
||||
? await db
|
||||
? await timePreparation("project_context", () => db
|
||||
.select({
|
||||
id: projects.id,
|
||||
executionWorkspacePolicy: projects.executionWorkspacePolicy,
|
||||
@@ -2385,7 +2393,7 @@ export function heartbeatService(
|
||||
eq(projects.companyId, agent.companyId),
|
||||
),
|
||||
)
|
||||
.then((rows) => rows[0] ?? null)
|
||||
.then((rows) => rows[0] ?? null))
|
||||
: null;
|
||||
const taskProjectId = issueContext ? issueContext.projectId : contextProjectId;
|
||||
const taskProjectContext = await loadProjectContext(taskProjectId);
|
||||
@@ -2407,14 +2415,14 @@ export function heartbeatService(
|
||||
readNonEmptyString(context.interactionStatus) === "accepted")
|
||||
: false;
|
||||
const acceptedPlanWakeRoutingDecision = issueContext
|
||||
? await resolveAcceptedPlanWakeRoutingDecision({
|
||||
? await timePreparation("plan_wake_routing", () => resolveAcceptedPlanWakeRoutingDecision({
|
||||
db,
|
||||
companyId: agent.companyId,
|
||||
agentId: agent.id,
|
||||
issueId,
|
||||
acceptedPlanContinuationWake,
|
||||
contextSnapshot: context,
|
||||
})
|
||||
}))
|
||||
: null;
|
||||
if (acceptedPlanWakeRoutingDecision) {
|
||||
context.forceFreshSession = true;
|
||||
@@ -2434,17 +2442,17 @@ export function heartbeatService(
|
||||
} else {
|
||||
delete context.acceptedPlanWakeRouting;
|
||||
}
|
||||
const routineEnvContext = await getRoutineEnvForExecutionIssue(
|
||||
const routineEnvContext = await timePreparation("routine_environment", () => getRoutineEnvForExecutionIssue(
|
||||
agent.companyId,
|
||||
issueContext,
|
||||
);
|
||||
));
|
||||
let responsibleUserId: string | null =
|
||||
await resolveResponsibleUserIdForRun({
|
||||
await timePreparation("responsible_user", () => resolveResponsibleUserIdForRun({
|
||||
run,
|
||||
contextSnapshot: context,
|
||||
issueContext,
|
||||
routineEnvContext,
|
||||
});
|
||||
}));
|
||||
const identityContext = await timePreparation("run_identity", () => initializeRunIdentity(db, {
|
||||
companyId: agent.companyId,
|
||||
runId: run.id,
|
||||
@@ -3231,7 +3239,7 @@ export function heartbeatService(
|
||||
} = { current: null };
|
||||
try {
|
||||
const startedAt = run.startedAt ?? new Date();
|
||||
const runningWithSession = await db
|
||||
const runningWithSession = await timePreparation("execution_start", () => db
|
||||
.update(heartbeatRuns)
|
||||
.set({
|
||||
startedAt,
|
||||
@@ -3242,12 +3250,12 @@ export function heartbeatService(
|
||||
})
|
||||
.where(eq(heartbeatRuns.id, run.id))
|
||||
.returning()
|
||||
.then((rows) => rows[0] ?? null);
|
||||
.then((rows) => rows[0] ?? null));
|
||||
if (runningWithSession) run = runningWithSession;
|
||||
|
||||
// Pause Durability: flip to "running" ONLY if the agent is still invokable.
|
||||
// Atomic conditional UPDATE is the sole gate (no read-then-write); 0 rows => abort.
|
||||
const runningAgent = await db
|
||||
const runningAgent = await timePreparation("execution_start", () => db
|
||||
.update(agents)
|
||||
.set({ status: "running", updatedAt: new Date() })
|
||||
.where(
|
||||
@@ -3258,7 +3266,7 @@ export function heartbeatService(
|
||||
),
|
||||
)
|
||||
.returning()
|
||||
.then((rows) => rows[0] ?? null);
|
||||
.then((rows) => rows[0] ?? null));
|
||||
|
||||
if (!runningAgent) {
|
||||
logger.warn(
|
||||
@@ -3300,12 +3308,12 @@ export function heartbeatService(
|
||||
});
|
||||
|
||||
const currentRun = run;
|
||||
await appendRunEvent(currentRun, {
|
||||
await timePreparation("execution_start", () => appendRunEvent(currentRun, {
|
||||
eventType: "lifecycle",
|
||||
stream: "system",
|
||||
level: "info",
|
||||
message: "run started",
|
||||
});
|
||||
}));
|
||||
|
||||
const { currentUserRedactionOptions, appendIdentityRedactedLog, onLog, onPersistedNativeLog, onAdapterMeta, onAdapterEvent } =
|
||||
await timePreparation("run_output", () => runOutput.begin(currentRun));
|
||||
|
||||
@@ -9533,9 +9533,10 @@ export async function executePaperclipNativeSession(input: {
|
||||
}
|
||||
return preparedInput;
|
||||
};
|
||||
const stageAttachments = () => createRunPreparationTiming(runId, "native.controller")("attachment_staging", prepareAttachments);
|
||||
return needsWarmRetirement
|
||||
? await executePaperclipNativeSessionWithinScope(input, prepareAttachments, false, ownDiagnosticLease)
|
||||
: await executePaperclipNativeSessionWithinScope(await prepareAttachments(), undefined, false, ownDiagnosticLease);
|
||||
? await executePaperclipNativeSessionWithinScope(input, stageAttachments, false, ownDiagnosticLease)
|
||||
: await executePaperclipNativeSessionWithinScope(await stageAttachments(), undefined, false, ownDiagnosticLease);
|
||||
} catch (error) {
|
||||
executionFailure = error;
|
||||
if (error instanceof NativeControllerRecoveryWaitingError) {
|
||||
@@ -9692,7 +9693,8 @@ async function executePaperclipNativeSessionAttempt(
|
||||
: null;
|
||||
const stagedRestart = input.restartRecovery?.kind === "reattach_remote_runner"
|
||||
? await verifyStagedAttachmentRestart(input) : null;
|
||||
if (input.useRunnerd) await restoreWarmRunnerdController(input);
|
||||
const timeController = createRunPreparationTiming(input.execution.binding.runId, "native.controller");
|
||||
if (input.useRunnerd) await timeController("warm_restore", () => restoreWarmRunnerdController(input));
|
||||
const identityRedactor = createAgentIdentityRedactor(input.runnerEnvironment?.PAPERCLIP_AGENT_PRIVATE_KEY);
|
||||
const redactIdentityText = (text: string) => redactSensitiveText(
|
||||
identityRedactor.chunk("diagnostic", text) + identityRedactor.finish("diagnostic"),
|
||||
@@ -9718,7 +9720,6 @@ async function executePaperclipNativeSessionAttempt(
|
||||
"paperclip_runner_provider_unsupported: ACPX candidate requires exact host qualification authorization",
|
||||
);
|
||||
}
|
||||
const timeController = createRunPreparationTiming(input.execution.binding.runId, "native.controller");
|
||||
const preparationSpans = input.preparationSpans ?? [];
|
||||
const preparationStarts = nativeRunPreparationStarts(
|
||||
preparationSpans,
|
||||
@@ -9772,20 +9773,22 @@ async function executePaperclipNativeSessionAttempt(
|
||||
startedAtMs: environmentStartedAtMs,
|
||||
})
|
||||
: null;
|
||||
for (const span of preparationSpans) {
|
||||
const rootMilestone = isNativeRunRootHistoricalSpan(span.name);
|
||||
await trace.record({
|
||||
...span,
|
||||
parentName: rootMilestone
|
||||
? "task.provider_session"
|
||||
: environmentSpans.includes(span)
|
||||
? "environment.startup"
|
||||
: "task.prepare",
|
||||
});
|
||||
}
|
||||
if (environmentScope) {
|
||||
await trace.end(environmentScope, { endedAtMs: environmentEndedAtMs });
|
||||
}
|
||||
await timeController("span_persistence", async () => {
|
||||
for (const span of preparationSpans) {
|
||||
const rootMilestone = isNativeRunRootHistoricalSpan(span.name);
|
||||
await trace.record({
|
||||
...span,
|
||||
parentName: rootMilestone
|
||||
? "task.provider_session"
|
||||
: environmentSpans.includes(span)
|
||||
? "environment.startup"
|
||||
: "task.prepare",
|
||||
});
|
||||
}
|
||||
if (environmentScope) {
|
||||
await trace.end(environmentScope, { endedAtMs: environmentEndedAtMs });
|
||||
}
|
||||
});
|
||||
let retainedTransition: VerifiedWarmTransitionBinding | undefined;
|
||||
let remoteWarmRecovery: RemoteWarmSessionRecovery | undefined;
|
||||
const cachedReleasedOwner = warmNativeSessions.get(nativeSessionScopeKey(input.execution));
|
||||
@@ -9870,7 +9873,7 @@ async function executePaperclipNativeSessionAttempt(
|
||||
}
|
||||
}
|
||||
if (input.useRunnerd) {
|
||||
retainedTransition = await migrateRunnerdStateRootForExecution({
|
||||
retainedTransition = await timeController("state_migration", () => migrateRunnerdStateRootForExecution({
|
||||
db: input.db,
|
||||
execution: input.execution,
|
||||
preparedExecutionAdmission: input.preparedExecutionAdmission,
|
||||
@@ -9881,7 +9884,7 @@ async function executePaperclipNativeSessionAttempt(
|
||||
restartRecovery: input.restartRecovery,
|
||||
runnerExecutionTarget: input.runnerExecutionTarget,
|
||||
bootstrapRetryLeaseOwner: input.leaseOwner,
|
||||
});
|
||||
}));
|
||||
}
|
||||
const durableRunnerBinding = input.useRunnerd
|
||||
? loadRunnerdDurableBinding(input.execution, retainedTransition)
|
||||
|
||||
Reference in new issue
Block a user