Preparing today's journal

Please wait a moment.

Back to stories

From Talking Pet to Approved Development Collaborator — Four Gates Built by Failure

How runner failures and costly multi-pet execution helped us separate conversation, editing, repository delivery, and PR merging.

Read like a book
Normal
An evening workbench with a black dachshund figure, four gates for conversation, editing, delivery and approval, blank audit cards, and a blank-screen laptop

Topic

Haru Space Lab

From Talking Pet to Approved Development Collaborator — Four Gates Built by Failure

How runner failures and costly multi-pet execution helped us separate conversation, editing, repository delivery, and PR merging.

Summary

Summary

  1. We separated ordinary pet conversation from explicit work proposals so a casual message could not become a development job by accident.
  2. We gave code editing, repository delivery, and PR merging distinct permissions and approval stages, while leaving production deployment outside the automated flow.
  3. Stale status, an invalid temporary work folder, Windows isolation failures, and multi-pet overhead taught us to value recoverability before autonomy.
12Page
An evening workbench with a black dachshund figure, four gates for conversation, editing, delivery and approval, blank audit cards, and a blank-screen laptop

Summary

At a glance

  • We separated ordinary pet conversation from explicit work proposals so a casual message could not become a development job by accident.
  • We gave code editing, repository delivery, and PR merging distinct permissions and approval stages, while leaving production deployment outside the automated flow.
  • Stale status, an invalid temporary work folder, Windows isolation failures, and multi-pet overhead taught us to value recoverability before autonomy.

This retrospective reconstructs July 22–28, 2026 from Git history, design documents, and incident records. It omits user conversations, identifiers, internal addresses, and secrets that do not belong in a public story.

An AI pet that talks well in a chat room and an AI pet that can act as a development collaborator are two very different things.

If an answer is wrong, I can ask again. Editing changes files. A commit leaves a record in a repository. A merge changes the baseline for everyone else. Production deployment reaches the service people actually use. The responsibility behind the same phrase—“please do this”—became heavier at every stage.

At first, it seemed sufficient for the pet to understand a request, inspect a repository, and report the result. A week later, I saw the problem differently.

What turned the AI pet into a development collaborator was not more autonomy. It was four gates placed in front of four different responsibilities.

Those gates were conversation, isolated editing, repository delivery, and PR merging. Production deployment remained a separate decision beyond them. The structure became concrete not through a smooth success story, but through several failures.

The first goal was not merely to hide developer screens

Not everyone using a pet in Haru Space should have to understand branches, sandboxes, and worktrees. People should be able to speak normally in a room: organize this idea, review this code, or change this feature.

We therefore described the work in terms of outcomes.

  • Read-only work that organizes an idea
  • Review work that inspects the current code
  • Edit work that changes actual files

Each space had one representative work room. A single chat message was updated as the request moved through its states. We did not pour model reasoning, shell commands, and raw logs into the conversation. On completion, the room received a short summary, while a sanitized result accumulated in the space’s documents.

This was more than a UI exercise that made technical complexity look simple. It separated information people did not need to see from information they should not see. Raw logs can contain local paths, account details, or secrets. An approver still needs enough of a summary to judge what is about to change.

Instead of copying an entire prompt into the work history, we retained a length-limited, sensitive-data-filtered request summary and the minimum operational record: state, change fingerprint, and decision actor. We wanted explainability without collecting more private source material in the name of explanation.

The first gate: a conversation must not be mistaken for a job

The early implementation classified conversation, planning, review, and editing from the pet’s assigned role and the wording of a message. If someone told a developer pet, “What if we made this screen?”, the system could create a real edit-approval card.

It looked clever from a technical perspective. AI was inferring intent and choosing an execution flow. From a product perspective, it was unsafe. A person might only be exploring an idea while the system interpreted the sentence as a request to change files.

The lesson was direct.

Execution intent is not something the system should infer from a sentence. It is a state the person should confirm through a separate action.

We changed ordinary @pet mentions so they remained conversation even when the sentence included words such as “build” or “review.” Development work could begin only from an explicit work flow inside a work space, or after a group-room proposal was sent to a chosen work space and accepted by someone in its work room.

A need discovered in a group conversation did not become a code job immediately. The sender first chose the destination. Someone in the receiving work room reviewed and accepted or rejected it. Acceptance still did not bypass the normal edit approval.

This separation added an action, but it made responsibility much clearer. A human-confirmed boundary now stood between “I talked with an AI” and “I assigned a change to an AI.”

The second and third gates: edit execution and repository delivery were not one approval

After human approval, an edit ran in an isolated folder created for that request. Read-only analysis and writable editing also used separate threads. Even when context continued from a previous conversation, a low-permission thread could not quietly become a high-permission one.

There was another trap. Approval to execute an edit happens before the reviewer has seen the result. If that same approval also authorizes commit and push, an unreviewed result reaches the repository.

We split edit approval from repository-delivery approval.

  1. A person approves execution of the edit.
  2. The AI changes and checks files inside the isolated folder.
  3. The system fixes a fingerprint from the changed file list and contents.
  4. By default, a person reviews the result and decides whether to approve commit and push. Only when the group has explicitly enabled an automatic-delivery policy can the group policy actor replace this decision.
  5. A separate delivery runner rereads the work, verifies the same fingerprint, and writes only to a work branch.

The Codex process did not receive repository write credentials. Commit and push belonged to a Delivery Runner with a different execution identity and narrowly limited credentials. Direct pushes to the base branch and force pushes were not allowed.

This separation was less convenient. There were more stages, and if the delivery runner stopped, a job could finish editing while remaining absent from the repository. We still did not combine the credentials for convenience. An outage can be recovered; a missing boundary can spread the wrong change much faster.

The fourth gate: creating a PR was not the end

Once a work branch existed, we needed a PR and merge flow. Again, “push succeeded” and “change the base branch” did not become the same decision.

The requester selected a group chat they participated in. Haru Space created a PR through the verified repository connection and kept the work summary, artifact, and decision actions in one message in that room. Immediately before merge, the service rechecked the repository, source and target branches, and head SHA. If the PR changed after approval or repository protection rules refused it, Haru Space did not merge it.

Rejection did not end as a one-word failure state. It required a reason. Rework began from the rejected PR’s source branch while preserving the original protected branch as the final merge target. The system neither secretly rewrote the old PR nor overwrote the past decision with success.

This distinction also mattered on screen. When the rework “starting branch” and final “merge target” were shown as a single base branch, people could not tell what they were continuing. Once the fields and labels were separated, a rejection reason could become an actual input to the next job.

Production deployment still did not follow automatically. Merged code and permission to change the environment people use are different responsibilities.

Status was not the fact; it was a view of the fact

The first major operational failure was not simply that an AI job failed. The runner had already failed, but the screen continued to look as if the job were waiting.

An authentication problem ended the job on the execution side. Haru Space, however, reconciled remote state only when someone opened a particular work item. Once the pop-up was closed, a remote failure and the chat card could remain out of sync. A Windows scheduled task could also say it was running without proving that its child process was healthy.

I had treated the state stored in the application database as truth. It was actually a projection optimized for people to read. The execution system held another fact, and synchronization between the two could fail.

The recovery was not unlimited real-time polling. When a room or work view opened, the application checked only a bounded number of older unfinished jobs. It shared results briefly per room and capped the number checked at once. For a failed job, it did not recreate an entire prompt that we had deliberately not stored. It restored only the sanitized request summary, previous work type, and pet selection into a new form. The person read and confirmed it before creating a new job.

The status card became more accurate not because we polled everything more often, but because we admitted that it was a copy and defined when and how it should reconcile with its source.

An isolated work folder disappeared, and a safety layer stalled

The second failure came from registering a disposable work folder as the project’s permanent root. Per-job folders are supposed to be removed after their work is complete. The runner nevertheless continued to treat the deleted temporary path as the source repository, so new jobs failed before a model was even called.

It was not enough for a configuration value to “look like a Git repository.” The runner had to know whether it was a permanent project root or a temporary folder attached to another checkout. We added validation at startup and repeated it when preparing a job. A bad setting now stopped as a clear configuration error before consuming queued work.

In another edit, the Windows isolation helper repeatedly failed while preparing the firewall policy needed for the editing tool. The environment recovered later in the same run, but the original edit was not automatically resumed.

The easiest workaround would have been to turn isolation off. We did not. We left actual write preflight, bounded retries for transient initialization errors, and same-folder edit resumption as follow-up work toward a safer recovery path.

Keeping a safety mechanism after the day it causes a failure is one of the harder parts of approval-based automation.

More pets produced more waiting, not more collaboration

At first, it seemed reasonable to divide coordination, planning, design, development, review, and documentation among several pets. The UI selected several of them, and the prompt asked each role to finish one stage and hand off to the next.

In one incident analysis, the base run and role-specific runs produced five SDK sessions and exhausted a fifteen-minute limit. A different, narrower single run had completed in about forty-two seconds, but the requests were different, so those numbers were not a direct performance comparison. What was clear was that a separate session for every role increased waiting, logs, and token use.

We changed what a “role” meant.

  • A role does not define which capabilities a pet is forbidden to use.
  • A role defines the area it should examine with extra care.
  • One request is handled by one pet and one SDK thread by default.
  • If a second-model review is warranted, it is requested as a separate review after a risky result exists.

This did not abandon pet collaboration. It stopped us from confusing simultaneous model calls with collaboration. For a small PoC, one execution with coherent context and a clear owner fit the available resources and the user experience better.

We kept the permission split even when the delivery runner stopped

Late in the week, a code job finished and received approval, yet the repository-delivery queue did not move. The coding agent was alive; the separate Delivery Runner was not. Because it had started only at sign-in, an abnormal exit could leave it asleep until the next sign-in.

We added bounded automatic restart monitoring and periodic recovery checks. If one instance was already running, the system did not create another. Logs omitted prompts and credentials.

Around the same time, a small gateway server restarted after an outage but its service still could not come back. An abnormal shutdown had left the last line of an append-only record incomplete, and startup stopped while reading the file. We preserved the valid prefix and isolated only the incomplete tail. We did not make the service silently skip corruption in the middle of the history.

Both incidents exposed the same truth. A process marked “running,” a powered-on server, and a responding HTTPS endpoint are different from a healthy end-to-end work flow. The Agent, Delivery Runner, gateway, and last successful job needed separate signals.

Measuring cost did not require storing the conversation

As AI work grew, we added an append-only usage ledger that could aggregate by user, group, pet, model, and work type. The important part was what it did not record.

It did not store prompts, responses, chat text, or document contents. Instead, it kept only aggregation metadata: user and group scope, pet, model, execution type, result linkage, input and cached-input tokens, output and reasoning-output tokens, status, and time. A unique result key prevented refreshes and concurrent status checks from inserting the same usage twice.

We described these numbers as usage for cost observation, not as the invoice itself. Price versions, contracts, and cache policy can differ. Failure to record usage also remained a supporting-path failure; it did not turn a completed conversation or development job into a failure.

Cost visibility and private conversation retention did not need to be traded against each other. Once we defined what we needed to calculate, the original text was unnecessary.

A checklist left by the failures

If I started that week again, I would check the following in this order.

1. Put an explicit human action between conversation and execution

Do not infer execution intent from natural language. Create a work record only after a person confirms its target, scope, and intended result.

2. Divide permission again at every consequential stage

Do not bundle reading, editing, repository writing, merging, and production deployment into one “development permission.” Give each stage its own identity, credentials, and approval basis.

3. Treat UI status as a projection of an external fact

Assume the runner and application can disagree. Design bounded reconciliation, idempotent updates, and a clear recovery entry point.

4. Separate temporary work-folder lifetime from project configuration

Validate permanent checkouts and per-job isolation folders as different kinds of paths. Automate cleanup, but do not delete unreflected changes before deciding whether they must be preserved.

5. Validate one accountable agent before multiplying agents

The number of roles is not quality. Let one execution own the result end to end, then add an independent review as a separate job when risk justifies it.

6. Preserve rejection as an input, not an ending

Keep the reason, starting branch, and final target distinct. Continue with a new history instead of overwriting an old failure with success.

7. Do not merge credential boundaries just because recovery is inconvenient

If a separated runner stops, recover that runner. Do not solve the outage by giving repository and deployment credentials to the AI process.

The moment it became a development collaborator

At the end of July 22–28, the AI pet was still not a fully autonomous developer. It did not change production on its own, erase a rejected decision, or put an unseen change into the base branch.

It had become something more useful.

A person’s ordinary request could be confirmed as an explicit work proposal. Inside an approved scope, the pet could produce an isolated change, leave a reviewable result and fingerprint, and carry a rejection reason into the next job. When it failed, we were beginning to see where it stopped and how to start again.

At first, I thought a pet became a development collaborator when I gave it more work. Looking back, the opposite was true.

It became a collaborator when we agreed on how far it could go, who had to open the next gate, and how a failure would remain on the record.

Continue reading

Previous story · Next story

Previous storyWe Built Boundaries Before Buttons — Platform Authority, Group Membership, and Human ApprovalNext story Beyond One Repository: How Each Group Came to Own Its Project