Preparing today's journal

Please wait a moment.

Back to stories

What Must an AI Job Preserve So It Can Resume?

Dependency approvals, broken connections, and exhausted context taught us to preserve intent, changes, decisions, and scope separately—not inside one long conversation.

Read like a book
Normal
A calm editorial illustration in which a broken thread reconnects through work cards, a lockfile drawer, and a preservation box

Topic

Haru Space Lab

What Must an AI Job Preserve So It Can Resume?

Dependency approvals, broken connections, and exhausted context taught us to preserve intent, changes, decisions, and scope separately—not inside one long conversation.

Summary

Summary

  1. We separated recovery rules so a job could return to its own context and isolated changes after dependency approval.
  2. A verified lockfile cache and workspace-local dependency layout reduced repeated setup time and permission conflicts.
  3. We stopped carrying every long conversation forward: scheduled runs began with fresh context, while durable facts moved into repository records.
12Page
A calm editorial illustration in which a broken thread reconnects through work cards, a lockfile drawer, and a preservation box

Summary

At a glance

  • We separated recovery rules so a job could return to its own context and isolated changes after dependency approval.
  • A verified lockfile cache and workspace-local dependency layout reduced repeated setup time and permission conflicts.
  • We stopped carrying every long conversation forward: scheduled runs began with fresh context, while durable facts moved into repository records.

This Haru Space development retrospective reconstructs August 3–9, 2026 from design documents, Git history, and incident records. It omits private conversations, job and thread identifiers, local paths, internal execution details, and credentials.

When an AI job stopped midway, it seemed as though pressing “run again” should be enough. Real code work was not that simple.

Had the job changed files before it stopped? Was it waiting for approval to install an external dependency? Did the conversation context waiting on the other side of that approval still belong to this job? Had the repository’s base branch changed in the meantime? Would starting the same request again duplicate the changes?

At first, we thought continuity meant remembering a conversation for a long time. After a week of failures, we came to define it almost in reverse.

Continuity was not the ability to hold on to the longest conversation. It was a design that preserved intent, changes, approvals, and execution scope in separate ledgers—and safely cut away context that was no longer useful.

Dependency approval returned us to the wrong job

The first problem appeared in work that required approval for an external package.

When the AI needed a package that was not already in the repository, the job paused. A person checked the exact name and version and approved it. A constrained installer was then supposed to prepare the package in the same isolated workspace and resume the original job.

The early resume flow, however, looked for “the last conversation this pet used in this room.” If another job ran in the same room while the first was waiting for approval, that pointer could change. A job that had been writing an article could resume inside another job’s context, or finish as if its only purpose had been to inspect a dependency.

The mistake was assuming that the same room and pet implied the same job context.

We changed the model to job-specific continuity. Only the authenticated runner could recover the context left by the original execution and continue in the same isolated workspace. Older records without job-specific context did not borrow another job’s conversation. They recovered in a fresh context containing one sanitized copy of the original request.

During recovery, provisional output from before approval was not published as a final artifact. Until the job reached a genuine terminal state, the chat retained only the original request and the current progress state.

Continuity became safer when rejection was possible

The same incident revealed that the final PR lacked a visible rejection action. When the requester of a scheduled job and the group administrator were the same person, the early UI applied one self-approval restriction to every stage.

Self-approval before code runs and rejection after reviewing a final PR do not mean the same thing. After seeing the result, an authorized person must be able to decide, “Do not apply this change.” The server allowed that decision, but the screen hid it.

We separated the approval stages. At the final integration gate, an authorized group administrator could reject even their own request and record a reason. We did not rewrite an old failure or a closed PR as a success. A surviving remote branch and its artifacts could inform later work, but a new decision had to create a new record.

Continuity was not a feature that blindly continued everything. It included the right to stop, reject, preserve the reason, and restart from the correct point.

We replaced per-job installation with a fingerprint

Isolated workspaces kept code changes from mixing, but they also repeated the cost of dependency setup. Every new workspace began without packages, bringing back approval steps and network delays.

When we tried to attach the host machine’s shared package store directly, account permissions crossed boundaries and caused read failures. Links that pointed to a cache outside the project were also rejected by the build tool’s project-root checks. Sharing intended to improve speed was undermining isolation and reproducibility.

We changed the cache criterion from “this folder already exists” to “these verified dependency inputs are identical.”

  • Build a project fingerprint from package declarations, the lockfile, and allowed configuration.
  • Use only the package-manager version pinned by the repository and approved distribution sources.
  • Perform the initial preparation from a temporary copy of the inputs, not the source checkout.
  • Do not run install scripts or user-defined hooks.
  • Reuse only package data that has passed the high-risk vulnerability check, and expose it read-only.
  • Give every isolated workspace a self-contained dependency layout inside the project boundary.

The next job with the same fingerprint did not repeat downloads and a full installation. When the lockfile changed, the system prepared a new cache version instead of modifying the old one. A genuinely new dependency still went through human approval.

The cache was not a fast bypass. It was reuse of a verified result.

We refreshed the baseline without overwriting user changes

A dynamic project could not keep using the default branch from the day it was first connected. New work should begin from the latest authoritative repository whenever possible. Yet automatically forcing a baseline checkout into sync while local changes remain could destroy someone’s work.

We therefore checked the remote baseline immediately before preparing a job. If the baseline checkout contained an unexpected change, the process stopped safely instead of cleaning it automatically. Creating a new isolated job and discarding existing user work were not the same authority.

This principle reappeared in later recovery work. The system did not have permission to “make the original current” at any cost. Its responsibility was to choose a safe baseline for new work.

Progress state was not raw logging

As AI jobs grew longer, users had to wait behind a single “working” message. It was hard to tell whether a job was moving, waiting for approval, or running verification. But exposing raw logs would reveal local paths, commands, and internal errors while rapidly filling the chat.

We structured only an allowlisted set of progress stages and updated them in one status message. The selected job’s detail view kept sanitized stages and timestamps in order. Realtime transport carried only lightweight state; the authenticated detail API rechecked the actual record. Low-frequency polling took over when the connection failed.

Later, one job had a sanitized failure diagnosis in the bridge, but the Haru Space conversion layer discarded it and showed only a generic error. We included a length-limited, secret-masked explanation in the result and distinguished outcomes whose body was the same but whose diagnosis differed.

Recovery needed useful explanation and a boundary around raw information that must never be public.

Closing an external sign-in window no longer meant starting over

Repository connection during development onboarding leaves the app for an external authentication window and then returns. If a user cancelled authentication or closed the webview, the Haru Space dialog remained locked in a “connecting” state. Reopening the service also lost the visible place where the journey had stopped.

We left a small resume marker in the same browser containing only the group and stage timestamps. It stored no token, repository credential, prompt, or user content. On return, the screen used the marker as guidance, but recalculated the actual repository connection and runner readiness from server records.

A client-side mark saying external authentication succeeded was not evidence of authority. We remembered the return point and reverified the facts.

This did not continue across devices. Its scope was recovery within the same browser. Cross-device continuity remained a separate privacy question: which state, if any, should belong to an account?

The first job in an empty repository also ended as “complete”

For a new project’s first job, we placed a verified starter in the workspace. Sometimes the AI inspected it, explained it, and stopped without changing a file. The response looked successful, but the editing objective had not been achieved.

Only when the starter and result fingerprints matched, and only for this first-edit condition, did we allow one corrective run in the same workspace and context. It had to produce an actual change, a structured dependency request, or a clear failure. A second unchanged result was not dressed up as success.

The corrective run gained no network or repository-delivery authority. Its usage was combined with the first run in the ledger. Recovery could not become a hidden way to create an unrecorded new job.

Context that was too long became debt, not preservation

At the end of the week, a scheduled writing job stopped without changing files. Scheduling and automatic approval had completed normally, and the runner had started the job, but a long-lived shared conversation was attached to the new request. Its execution budget was exhausted while processing current research, images, and three language variants.

The incident overturned the idea that continuing a conversation is always better.

Every scheduled run began in fresh job context from then on. Durable information came from repository documents, the current request, and a bounded reference set. Ordinary conversational continuity remained, while hidden accumulation from repetitive automation was cut off.

Another job lost its response stream before completion. The principle identified at the time was to inspect the workspace before automatically running anything again. If nothing changed, a new job may be appropriate. If files remain, continuing verification and completion in the same job avoids duplicate edits. That bounded recovery remained future work at the time, so we do not record it as a completed feature.

What we chose to keep—and what we chose to discard

A resumable AI job needed the following.

What must remain

  • The human-reviewed request and its execution scope
  • Job-specific isolated changes and Git state
  • Dependency requests and decisions, plus the verified lockfile fingerprint
  • The current stage, a bounded failure explanation, and an append-only decision history
  • The baseline repository revision and result fingerprint
  • A reference to job-specific context when recovery is valid

What must not remain

  • A shared “last conversation” pointer used by unrelated jobs
  • Repeated copies of full prompts and raw model logs
  • Authentication tokens, local paths, and internal commands
  • Endless historical conversation attached to scheduled work
  • A state that makes provisional output look final

What must be verified again

  • Current access to the group and work room
  • The baseline branch and repository state
  • The approved fingerprint and dependency inputs
  • Whether any file change actually exists
  • Whether the job has truly reached a terminal state

What “resume” came to mean

The failures of August 3–9 all sat beneath one word—“resume”—but their causes differed. One job returned to the wrong conversation after dependency approval. Another repeated installation every time. Onboarding was stranded in an external authentication window. A first edit ended without an edit. A scheduled job could not begin useful work because its context was too large.

That was why one retry button could not solve them all.

The job’s purpose belonged in the request record, changes in the isolated workspace, decisions in the audit ledger, and reproducibility in the lockfile fingerprint. Conversation context was only one part of the system. We would continue it when valid and start clean when it belonged to another job or merely raised cost.

Making an interrupted AI job resumable did not mean teaching it to remember everything. It meant finally distinguishing what we could not afford to lose from what we needed the courage to cut away.

Continue reading

Previous story · Next story

Previous storyOpening the Production Domain Began Behind the ScreenNext story Onboarding Did Not End on the Last Screen