---
title: "Building a Persistent AI Engineering Agent"
description: "How to build an agent that picks up a ticket, works across six repositories for days, survives restarts and produces reviewable pull requests. Make the work persistent, not the model."
date: "2026-08-31"
type: concept
topics:
  - "Agentic Systems"
  - "Engineering"
  - "AI Governance & Security"
canonical: "https://dalugoda.com/persistent-engineering-agents"
series: "Working concepts"
sources:
  - "https://modelcontextprotocol.io/specification"
  - "https://docs.temporal.io/encyclopedia/detecting-workflow-failures"
  - "https://git-scm.com/docs/git-worktree"
  - "https://docs.github.com/en/webhooks/about-webhooks"
  - "https://arxiv.org/abs/2604.04522"
  - "https://datatracker.ietf.org/doc/draft-helixar-hdp-agentic-delegation/"
  - "https://theagenticsprint.com"
  - "https://helixar.ai/about/labs/hdp/"
---

## The model does not need to run forever

A few days ago I wrote about the agent that never really logs off, and what happens to [identity, delegated authority, memory and behaviour](/persistent-ai-agent-authority) once an agent persists across days rather than minutes. That piece was about the governance problem.

This one is about the other side of it. How would you actually build one?

Not a demonstration where a coding agent runs in a terminal for six hours. A real persistent engineering agent: one that can pick up a Jira ticket, understand a system spread across several repositories, work on it over several days, survive restarts, wait for humans and for CI, resume when something changes, coordinate work across repository boundaries, and eventually produce a set of reviewable pull requests.

I have been thinking about this in the context of [the Agentic Sprint](/agentic-sprint), though the architecture is useful well beyond software delivery. The first design decision is the one that simplifies everything after it:

> Do not make the model persistent. Make the work persistent.

### In brief

- A persistent agent is not a long-lived model session. Processes die, credentials expire and models get replaced, so persistence has to live somewhere other than a conversation.
- The unit of work is the change, not the repository. A task that crosses six repositories needs a representation that exists outside any repository or model session.
- An explicit repository graph lets an agent reason about blast radius before it edits anything. Context tells an agent what exists; structure tells it what a change will touch.
- `AGENTS.md` is context, not enforcement. Instructions go to the model; authority is established by the infrastructure around it.
- The checkpoint matters more than the conversation. If a fresh model instance cannot continue the task without the previous transcript, the system is not persistent yet.
- The right shape is a persistent orchestrator running persistent work through deliberately ephemeral workers.
- Sleeping is a feature. A persistent agent should be event-driven, not a loop asking whether anything has changed.
- Credentials should be leased for the length of a work item, not the length of the task. This is [an authority lease](/persistent-ai-agent-authority) applied to the engineering system itself.

## 1. Persistence is not an infinite session

The obvious implementation is to start a coding agent, give it a ticket, keep the process alive, keep feeding it context, and hope it finishes.

I do not think that is a good architecture, because everything it depends on is unreliable:

- processes die
- containers restart
- context windows fill
- authentication expires
- machines disappear
- providers have outages
- models get deprecated and replaced.

If the identity and memory of your engineering agent exist entirely inside one conversation, that conversation has accidentally become your workflow engine, your database and your state machine. It is none of those things, and it has no transactional guarantees.

The alternative is to demote the model. It becomes a cognitive runtime: something you invoke to reason, plan, write and review, and then discard. The persistent thing is the task.

```text
GitHub / Jira
      ↓
Persistent orchestrator
      ↓
Durable task state
      ↓
Workspace and repository graph
      ↓
Coding agent (Claude, Codex, whatever comes next)
      ↓
Code, tests, pull request
      ↓
Checkpoint
      ↓
Durable task state
```

## 2. The unit of work is the change, not the repository

Consider an ordinary enterprise application made of six repositories: `shared-contracts`, `customer-api`, `identity-service`, `staff-portal`, `customer-portal` and `terraform-platform`.

A ticket arrives:

> JIRA-142: add corporate account status across onboarding.

A human engineer reads that and immediately understands it is not confined to one repository. The contract changes, the API follows, then two front ends, and there is probably an identity dependency in there too.

This is where many coding-agent demonstrations stop resembling real engineering environments. They assume the repository is the unit of work. It is not. The change is the unit of work, and repositories are simply boundaries the change crosses.

So the system needs to represent JIRA-142 independently of any model session or Git repository:

```yaml
task_id: JIRA-142
objective: Add corporate account status across onboarding.
phase: implementation
status: active
repos:
  - shared-contracts
  - customer-api
  - identity-service
  - staff-portal
  - customer-portal
work_items:
  - id: WI-001
    repo: shared-contracts
    status: complete
  - id: WI-002
    repo: customer-api
    status: running
    depends_on: [WI-001]
  - id: WI-003
    repo: staff-portal
    status: blocked
    depends_on: [WI-001, WI-002]
```

That is the important move. The state of the engineering task now exists outside the model. The agent can disappear, the container can disappear, the provider can have an outage. The task remains.

## 3. The repository graph matters more than the prompt

Multi-repository work introduces a second problem. Giving an agent access to six repositories is not the same as giving it an understanding of how they relate.

I would maintain an explicit repository graph:

```yaml
repos:
  shared-contracts:
    type: library
    consumers: [customer-api, staff-portal, customer-portal]
  customer-api:
    type: service
    depends_on: [shared-contracts, identity-service]
  identity-service:
    type: service
  staff-portal:
    type: frontend
    depends_on: [customer-api, shared-contracts]
  customer-portal:
    type: frontend
    depends_on: [customer-api, shared-contracts]
```

With that in place, a proposed modification to `shared-contracts` stops being "change this repository" and becomes a question about blast radius: three consumers, two of them transitively through the API.

:::figure repository-graph
The blast radius of a change to a shared contract library: two front ends and one service consume it directly, and the front ends also depend on the service, so a contract change reaches five repositories through two paths.
:::

That graph can drive impact analysis before a line of code is written. This is one of the places where I think agentic software development still has to mature. A great deal of current effort goes into giving agents more context. At some point we have to give them more structure instead.

## 4. Instructions are context, infrastructure is authority

Repository-level files such as `AGENTS.md`, `CLAUDE.md` and an architecture note are genuinely useful, and I use the pattern myself. Each repository can explain its local responsibilities:

```text
customer-api

Responsibility
  Customer lifecycle and account operations.

Allowed dependencies
  shared-contracts, identity-service

Forbidden
  direct access to the identity database
  breaking public contract changes
  synchronous notification dependencies

Build
  dotnet test Customer.sln
```

That is good context. It is not a boundary.

Writing "never modify the identity database directly" into a Markdown file does not prevent an agent from doing it. It is guidance presented to a probabilistic system, and guidance is not a control.

So I would use these files for context and enforce the things that matter somewhere the model cannot argue with. The orchestration layer, not the prompt, should determine:

- which repositories can be read
- which repositories can be written
- which commands can execute
- which network destinations can be reached
- which credentials are available
- whether a pull request can be opened
- whether a pull request can be merged
- whether a deployment is possible.

The model gets instructions. The infrastructure establishes authority. Those are different things, and conflating them is how an agent ends up with more reach than anyone intended.

## 5. Separate system reasoning from repository execution

The tempting architecture is one large agent with all six repositories mounted, and a prompt saying "implement JIRA-142". It might even work. I still would not make it the normal operating model.

I would split the roles by what they are allowed to touch.

### Discovery reads everything and writes nothing

An architecture or discovery agent can read all six repositories and write to none of them. Its output is not code. It is:

- an impact analysis
- a dependency and sequencing plan
- architecture decisions worth recording
- a risk assessment
- acceptance criteria mapped to repositories
- a build plan.

### Implementation reads widely and writes narrowly

Once a human accepts that plan, specialised implementation agents get enough context to reason globally and only enough authority to act locally:

```text
Backend agent
  read    customer-api, shared-contracts, identity-service
  write   customer-api
  can     run tests, create a branch, commit, open a pull request
  cannot  modify identity-service, merge, deploy

Frontend agent
  read    staff-portal, shared-contracts, customer-api
  write   staff-portal
```

The asymmetry is the point. Wide reading produces good decisions. Narrow writing contains the damage when a decision is wrong.

## 6. Every task owns a workspace

I would not let an autonomous agent work in somebody's normal development clone. Every persistent task should get an isolated workspace, and [Git worktrees](https://git-scm.com/docs/git-worktree) suit this well:

```text
/workspaces/JIRA-142/
    shared-contracts/
    customer-api/
    identity-service/
    staff-portal/
    customer-portal/
```

The orchestrator then has deterministic knowledge of where the work exists, which matters when it has to resume something three days later. It also makes parallelism straightforward: JIRA-142 and JIRA-151 can both touch `customer-api` without touching each other.

## 7. The checkpoint matters more than the conversation

This is the practical heart of it. At the end of every meaningful execution cycle, the agent writes a structured checkpoint:

```text
JIRA-142 / WI-002 checkpoint

Objective   Implement corporate account status API.

Completed   Customer entity extended.
            Mapping implemented.
            GET endpoint updated.
            Unit tests added.

Remaining   Integration test failing: shared-contract package
            1.17.0 has not reached CI.

Decisions   ADR-142-03: represent AccountStatus as an enum
            rather than a string.

Changed     Customer.cs, AccountStatus.cs, CustomerMapper.cs
Tests       47 passed, 1 failed
Commit      8c27af9
Blocker     Waiting for WI-001 package publication.
Next        Resume integration tests after publication.
```

Then shut the model down.

Three hours later the dependency lands. A different machine starts a different model instance. It reads the task state, the build plan, the checkpoint, the repository state and the diff, and continues.

I think of this as externalised cognition. The agent's important knowledge is never trapped inside its conversation, because the system continuously converts reasoning into durable artefacts.

Native session resumption still has value. If a coding agent can pick up the same thread, preserving that working context is a real optimisation. But treat it as exactly that, an optimisation, and hold the architecture to one test:

> Could a completely new model instance continue this task without access to the previous conversation?

If the answer is no, the system is not persistent yet. It is a long conversation with good uptime.

That test also buys model portability. Today Codex, tomorrow Claude, next year something not yet released. The engineering task should not care.

:::figure persistent-task-lifecycle
The cycle a persistent task repeats: an event wakes it, a worker is granted temporary authority, work happens, a checkpoint is written back to durable state, and the worker and its credentials are destroyed while the task sleeps.
:::

## 8. Persistent orchestrator, ephemeral workers

This leads somewhere that feels counterintuitive at first. I do not want one large persistent coding agent at all. I want a persistent orchestrator managing persistent work through ephemeral specialised agents.

:::figure orchestrator-workers
A ticket enters discovery and architecture analysis, produces a build plan that a human approves, then fans out to contract, backend and frontend agents working in parallel, each producing a pull request, before converging through integration and quality assurance to a second human gate and release.
:::

Every worker can die as soon as its job is done. The workflow stays alive.

Specialisation gets easier too, because the roles genuinely need different authority. An architecture agent needs breadth and no write access. A quality assurance agent should be able to trigger tests but not rewrite production code. A reviewer should be able to inspect every affected repository and modify none of them. Persistence lives at the orchestration level, and authority is scoped per role.

## 9. Events wake the agent, and sleeping is a feature

A persistent agent should not sit in a loop burning tokens asking whether anything has changed. Persistence should be event-driven.

When the backend agent reaches a blocker, the worker terminates and the task state becomes `waiting_dependency`. Later, GitHub emits a merge event for the pull request it was waiting on. The orchestrator receives the event, looks up the dependent work item, and schedules another execution. A fresh worker starts, reads the checkpoint and continues.

The same mechanism covers the whole surface a real engineering task waits on:

- a ticket is updated
- a pull request is approved or rejected
- CI succeeds or fails
- a security scan completes
- a human approval arrives
- a deployment completes
- a dependency is released
- a scheduled wake-up is reached.

So a persistent agent is asleep most of the time. That is not a limitation to engineer around. It is the correct behaviour, and it is what makes the running cost of a seven-day task proportional to the work rather than to the elapsed time.

## 10. Keep the model replaceable

Both Claude Code and Codex can serve as the execution runtime underneath this. I would not couple the orchestration system to either. A provider interface is enough:

```text
CodingAgent
  start()
  resume()
  execute()
  checkpoint()
  stop()
```

with a `ClaudeAdapter` and a `CodexAdapter` behind it. Then an individual work item declares what it needs:

```yaml
agent:
  provider: codex
  role: backend-implementation
```

while another asks for a different provider in an analysis role. The interesting abstraction is not which model is being used. It is what role is being executed, against what context, with what authority. That question survives model generations.

This gives me a fairly strong architectural opinion:

- If replacing one model with another destroys the agent's memory, persistence is coupled to the wrong layer.
- If swapping providers means the system forgets why a pull request exists, state is coupled to the wrong layer.
- If restarting a container means rediscovering six repositories from scratch, context is coupled to the wrong layer.

The persistent system should own identity, objective, state, memory, workspace, authority, dependencies, decisions and checkpoints. The model should provide reasoning, planning, coding, review and analysis. That separation is the whole design.

## 11. A control plane, not a credential handout

I would not give every worker direct credentials for every underlying system. This is where [the Model Context Protocol](https://modelcontextprotocol.io/specification) earns its place: instead of handing the model a database password, a GitHub token, Jira credentials and cloud keys, give it controlled tools.

```text
task.get              repo.inspect          github.create_branch
task.checkpoint       repo.checkout         github.create_pr
approval.request      ci.run                policy.check
approval.status       ci.status
```

The agent asks to create a pull request. A control service decides whether that particular agent, in that role, at that moment, is permitted to. The model never holds broad infrastructure authority, and every consequential action passes a point where policy can be applied and the request recorded.

## 12. Lease the credentials too

There is a security consequence worth stating on its own. A persistent task may live for seven days. That does not mean its worker needs seven-day credentials.

When a worker wakes, give it temporary authority scoped to the work item:

```text
Agent        backend-agent-142-04
Can read     customer-api, shared-contracts, identity-service
Can write    customer-api, branch agent/JIRA-142
Can          run approved build commands, commit, open a pull request
Cannot       merge, deploy, change repository settings, read production secrets
Lifetime     45 minutes
```

When the worker finishes, the credential expires. The persistent task survives; its operational authority does not.

This is exactly the [authority lease](/persistent-ai-agent-authority) idea applied to the engineering system itself, and the engineering system turns out to be an unusually good place to try it. The work is naturally bounded, the tooling already knows what a build needs, and the blast radius of getting it wrong is a failed pipeline rather than a payment.

## 13. Memory is not conversation history

Another mistake would be storing every previous model message and calling it memory. Useful persistent engineering memory is more structured than that, and it separates into at least four kinds:

| Kind | What it holds |
| --- | --- |
| Task | Objective, acceptance criteria, current phase, completed work, blockers, dependencies |
| Architectural | Decision records, system boundaries, repository relationships, API contracts, known constraints |
| Execution | Commits, test results, failed approaches, pull requests, build artefacts |
| Organisational | Coding standards, security policies, architecture principles, repository ownership, release requirements |

Conversation history can still exist. It just is not the canonical state, and nothing important should be recoverable only from it.

The failed approaches deserve a mention. A record of what was tried and did not work is one of the most valuable things a resumed agent can read, and it is precisely what gets lost when a session ends.

## 14. Keep the infrastructure boring

None of this needs exotic infrastructure. A first version can be a set of ordinary PostgreSQL tables: tasks, task runs, work items, dependencies, workspaces, repository state, agent runs, checkpoints, artefacts, approvals, decisions and events. Large artefacts go to object storage.

Git stays the source of truth for code. Jira stays the source of truth for business intent. GitHub stays the source of truth for review and merge state. The orchestrator stores the relationships between them, and that relationship is the valuable part.

You can prove the whole architecture with a workflow tool such as n8n: a ticket webhook creates a task and a workspace, launches a worker, captures the result, writes a checkpoint, and waits for a GitHub webhook to resume. That is enough to find out whether the design holds.

For a serious production implementation I would eventually want a durable workflow engine such as Temporal underneath. The reason has nothing to do with AI. Long-running workflows have to survive process failure, worker restarts, network partitions, timeouts, retries, human delays, multi-day waits, duplicate events and partial execution. Those are distributed-systems problems with well-understood solutions. Do not try to solve them with prompts.

## 15. Where this meets the methodology

This architecture is the implementation shape of something I have been developing as [the Agentic Sprint](https://theagenticsprint.com), which turns autonomous delivery into a controlled workflow with explicit stage gates: requirement, discovery, impact analysis, build plan, human approval, parallel implementation, integration, quality assurance, a human or policy gate, and release. Each stage produces durable artefacts, each stage has defined authority, and each transition can be inspected. Agents come and go; the sprint persists.

[Autonomous Loop](/autonomous-loop) covers a different part of the same problem: how a bounded execution loop keeps working, recovers its context and iterates toward an objective. Put the two together and you get something more useful than an infinitely running coding agent. You get a durable autonomous engineering process.

The governance ideas stop being theoretical here too. A persistent engineering actor for JIRA-142 has an identity, a declared purpose, an accountable owner, a lifetime, a set of readable and writable repositories, no merge or deployment authority, a current behavioural state and a last human attestation, which is the build-plan approval. It creates short-lived children, and each child receives less authority than its parent:

```text
Authority(child) ⊆ Authority(parent)
```

That is where [Human Delegation Provenance](/hdp) becomes useful rather than abstract. The ticket establishes an organisational objective, the approved build plan establishes the human authorisation, the persistent actor carries that delegation, and the commits and pull requests at the end can retain provenance back to it. A reviewer can ask which human authorised the change that produced this diff, and get an answer that is evidence rather than an assertion.

Behavioural detection lands in the same place. A frontend agent that normally runs component tests, and then one afternoon reads SSH keys, reaches for cloud credentials, edits Terraform and contacts an unfamiliar endpoint, still has a perfectly valid identity. Its behaviour is what changed. That is the distinction behind [continuous agent authority](/persistent-ai-agent-authority), and an engineering system is a good place to notice it, because the baseline is unusually legible: agents here have narrow, repetitive, well-understood jobs.

## 16. The interesting problem is not persistence

Building something that wakes a model every four hours is not difficult. The difficult part is deciding what happens after it wakes.

- What does it know?
- What changed while it was asleep?
- What authority does it currently hold?
- Which repositories are relevant now?
- What work is already complete?
- Which decisions are settled, and which are still open?
- What is it waiting for?
- What is it allowed to change?
- Who approves the next transition?
- How do we know another agent has not changed the same dependency underneath it?
- Can we reconstruct why the final code exists?

Those are the real persistent-agent problems, and none of them are model problems. They are systems-engineering problems, which is good news, because we know how to solve those.

## The persistent agent is mostly ephemeral

There is an odd conclusion hiding in all of this. The best persistent agent is composed almost entirely of temporary things: temporary model sessions, temporary containers, temporary credentials, temporary worktrees, temporary tool permissions, temporary child agents.

What persists is narrower and more valuable: identity, purpose, state, history, authority lineage and memory.

So "persistent agent" is slightly the wrong name. What is actually being built is a persistent autonomous actor implemented through ephemeral execution. That architecture is more resilient, more model-agnostic, easier to secure, easier to audit, and, importantly, much easier to stop.

We have spent a lot of effort trying to make models remember more: longer context, better memory, persistent conversations. Those things are useful. But for serious autonomous systems the stronger move may be to let the model forget safely. Let it disappear. Preserve the decisions, the evidence, the state and the authority. Then wake whatever intelligence is best suited to the next piece of work.

The model does not need to run forever. The work does.

## Open questions

I would rather publish this with the unresolved parts visible.

- How two persistent tasks touching the same repository should coordinate. Optimistic concurrency and a rebase is the obvious answer, and it is not obviously the right one when both changes are half-finished.
- Where the human gate sits when the build plan itself is wrong. Approving a plan is not the same as approving every change the plan implies.
- How much of the repository graph should be maintained by hand rather than derived from build files, and what happens when the two disagree.
- Whether a checkpoint should be written by the model, by the orchestrator observing the model, or by both, given that a model summarising its own work is exactly the maker-checker problem the methodology tries to avoid.
- How long organisational memory should live before it becomes a liability, since a coding standard that was corrected two years ago is now just a confident, wrong instruction.

## Disclosure

I am a co-founder and CTO of Helixar. Human Delegation Provenance is Helixar research and open-specification work, published through Helixar under the Apache 2.0 licence, and it is an active IETF Internet-Draft rather than an adopted standard. The Agentic Sprint methodology and Autonomous Loop are my own independent work. This article describes an architecture I am arguing for, not a product, and no part of it should be read as a claim about any employer's systems.

## Sources and attribution

- [Reference](https://modelcontextprotocol.io/specification)
- [Reference](https://docs.temporal.io/encyclopedia/detecting-workflow-failures)
- [Reference](https://git-scm.com/docs/git-worktree)
- [Source code on GitHub](https://docs.github.com/en/webhooks/about-webhooks)
- [Reference](https://arxiv.org/abs/2604.04522)
- [Reference](https://datatracker.ietf.org/doc/draft-helixar-hdp-agentic-delegation/)
- [Reference](https://theagenticsprint.com)
- [Canonical HDP research and specification from Helixar](https://helixar.ai/about/labs/hdp/)
