How to use a schema to work with several agents across different sessions
I work with Claude, Codex, Copilot and local models, and I run out of tokens in the middle of a task. This is the schema I use to keep the state of the work in two files, so another agent can continue. Download the skill and tell me how you do it.
- ai agents
- workflow
- claude code
- codex
- copilot
I am in the middle of a task, and then I see a message: I reached the 5 hour limit, and I have to wait until five in the afternoon. The work is half done, and I do not know how to continue. I am sure more than one of you has seen it.
I use Claude Code, Codex and GitHub Copilot every day, and I run local models with Ollama. For a long time each agent knew only its own session, and when the session ended, the knowledge ended with it.
The habit that fixed this for me is simple: the state of the work lives in two files in the repository, not in the context window of one agent. This is my way of working, not a survey of what exists. There is a skill you can download and use today: paired-plan, with its schema and its validator. I would like you to try it, and to tell me how it went.
How I split the work
Most of my work is research: reading code, comparing options, checking documentation. I use subagents for it, and the token use is high. The limit itself is not the problem. The problem is what happens at the limit. The agent knows which files it changed and what it was about to do, then the session stops and all of that is gone. I wanted to say "continue" to a different agent, on a different platform, and not lose the work. The one thing all my agents already share is the repository, so that is where the state has to live.
The roles are fixed:
- One agent plans. It has the large context window. It reads the issue, decides the order and writes the plan.
- Small agents execute. Each gets one task and a complete plan. My personal site repository says it in writing: at most five subagents at the same time, none above Haiku, each in its own git worktree and branch. A subagent ends with a review, a check before the pull request, and the pull request. It does not merge.
- Another agent reviews. On GitHub, the Codex and Copilot reviewers read the pull request, so models that did not write the code read it too.
The cheap agents keep the cost low, and the cross review catches what the author model misses. But this only works if the agents share what they know.
Two files
The plan is two files side by side:
docs/<name>-plan.mdis for why: the problem, the decisions with their reasons, the risks and how to undo the change. It is written for a person, or for an agent that starts with nothing.docs/<name>-plan.jsonis for where we are: the tasks, their status, who took each one, and what comes next.
The Markdown changes rarely. The JSON changes after every task. This is an excerpt:
{
"$schema": "https://miguelcolmenares.com/schemas/plan-state/v1.schema.json",
"planId": "export-retry",
"lifecycle": "multi-session",
"handoff": {
"nextAction": "Finish use-in-export: the upload call is wrapped, the test for the forced 503 is not written yet.",
"branch": "feature/export-retry",
"lastCommit": "a1b2c3d Add withRetry",
"uncommitted": "src/export.ts wrapped, src/export.test.ts not started",
"verify": "npm test -- export",
"updatedBy": "codex",
"updatedAt": "2026-01-16"
},
"workItems": [
{
"id": "retry",
"status": "in-progress",
"tasks": [
{
"id": "use-in-export",
"status": "in-progress",
"acceptance": "npm test -- export passes, and a forced 503 in the test succeeds on the second try",
"files": ["src/export.ts", "src/export.test.ts"],
"dependsOn": ["write-with-retry"],
"claimedBy": "codex",
"claimedAt": "2026-01-16T09:30:00Z"
}
]
}
]
}
Each field answers a question the next agent would otherwise have to guess:
statusandclaimedBysay who has the task, so two agents do not take the same one.acceptancesays how to check that the task is done. Without it, "done" means "the previous agent said so".filessays what the task changes, anddependsOngives the order.handoffsays where to start: the next action, the branch, the last commit, what is not committed, and one command that proves the next step worked.
With this, I can do part of a task in Claude, continue it in Codex when the Claude window is full, and let Copilot do the review. I can also continue the same plan with a local model. I have used the Ollama connector with Codex and with Claude Code, in the terminal, on Ollama models. The plan is two plain files, so it does not belong to any platform.
The schema, level by level
The excerpt shows one task and the handoff. This is the whole shape. A plan has three levels: the plan, its work items, and the tasks inside each work item. A work item can be a phase, a repository or a ticket, and the schema does not care which.
{
"$schema": "https://miguelcolmenares.com/schemas/plan-state/v1.schema.json",
"planId": "export-retry", // a short name for the plan
"title": "Add a retry to the export job",
"lifecycle": "multi-session", // or "single-pr"
"createdDate": "2026-01-15",
"lastUpdated": "2026-01-16", // changes after every task
"relatedDocs": ["docs/export-retry-plan.md"], // the Markdown, and what the next agent must read
"handoff": { "nextAction": "..." }, // where to start next
"taskTemplate": { // the steps every task follows
"steps": [{ "id": "implement", "description": "...", "status": "not-started" }]
},
"workItems": [
{
"id": "retry", // a phase, a repository or a ticket
"title": "Retry the upload in the export job",
"status": "in-progress",
"findings": [], // what was observed, measured or estimated
"tasks": [{ "id": "use-in-export", "status": "in-progress", "claimedBy": "codex" }],
"log": [] // what happened in this work item, append only
}
],
"changelog": [] // what happened in the whole plan, append only
}
Every level has a few required fields and some optional ones:
| Level | Required | Optional |
|---|---|---|
| Plan | planId, title, lifecycle, createdDate, lastUpdated, relatedDocs, taskTemplate, workItems, changelog | $schema, description, handoff, externalRef, metadata |
| Work item | id, title, status, findings, tasks, log | kind, path, notes, metadata |
| Task | id, status | notes, acceptance, files, dependsOn, claimedBy, claimedAt, externalRef, metadata |
| Handoff | nextAction | branch, lastCommit, uncommitted, verify, updatedBy, updatedAt |
| Log entry | date, summary | agent, metadata |
A few rules in the schema are there on purpose:
- Four statuses and nothing else:
not-started,in-progress,blockedandcompleted. A typo such asdonefails validation, and does not become a fifth status that nobody reads. - Unknown keys fail. Your own data goes in a
metadataobject. Custom keys at the top of the plan are not allowed. - The lists that drive the work cannot be empty:
workItems,taskTemplate.stepsand thetasksof each work item.changelogandlogcan start empty and grow. - Almost everything about handoffs is optional.
handoff,acceptance,files,dependsOn,claimedBy,claimedAtand theagentin a log entry came after the first version, so older plans still validate. - Two histories, on purpose. The
logof a work item says what happened in that unit. Thechangelogsays what matters for the whole plan, such as the work moving from one agent to another. - A finding has a kind and a source. Start it with observed, measured or estimated, then say where it came from. "The API is slow" teaches nothing. "Measured: the list call takes 4 s, with curl, on 2026-01-15" does.
externalRefis optional. It links a plan or a task to a GitHub or Jira issue, and the schema checks the form of the key and the address.
The taskTemplate needs a word, because it is easy to skip. It is the list of reusable steps that each task starts from, for example make the change, run the acceptance check, and update the JSON in the same commit. It lives once at the top, so each work item does not repeat it.
The schema does not check everything. It cannot say that a task in progress needs an owner, or that a handoff is missing. The validator prints those as warnings, which I show in the Use it section.
Subagents get a plan that stands alone
A subagent starts with no memory of the conversation, and a small model cannot fill the gaps. So the plan for a subagent must be complete by itself: the issue, the branch and the base branch; the files it may change, and the files it must not touch because another subagent works on them; what "done" means and which tests to write; the checks and commit rules before the pull request.
The task fields carry most of this. acceptance is "done", files is what the task may change, and the files of the other in-progress tasks are what it must not touch. dependsOn shows which tasks can run at the same time: the ones whose dependencies are all completed.
Each subagent works in its own git worktree and on its own branch from the base branch, never in the main checkout. In Claude Code I ask for this with isolation: "worktree", and my project instructions state it as a rule. The worktree lets several subagents run at once without touching each other's files on disk. The "do not touch" list is still needed, because two branches that change the same file will conflict when merged.
The subagent stops at the pull request. The main agent reviews it, and merges only when I ask.
Cheap models with a small context
The main agent has the large context window and the better model. A subagent gets a cheap model and a small, bounded context. This saves tokens, and it works because the plan carries the thinking.
- Claude Code lets me pin the model of each subagent. I pin Haiku, and my project instructions say a subagent never uses a model above Haiku.
- Codex and Copilot have no pin for a cheap subagent model in this setup. The model comes from the session, with
codex --modelor the model picker in Copilot. There the rule is a written instruction to the orchestrator, and I choose the cheap model myself. - The context stays small, because the subagent gets only the plan for its task, never the conversation.
- If a task is too hard for the cheap model, I split it into smaller tasks, or the main agent does it. I do not give the subagent a bigger model.
One question the convention leaves open is who edits the JSON when several subagents run in parallel. I think one writer, the main agent, is the simplest rule, and it is written in the entry file of the project.
The mistake: a rule nobody read
The first version had two files and two lifecycles: a short plan that is deleted with its pull request, and a long plan that stays. A few days later I asked an agent to "create the plan in multiple phases" for a rollout across several repositories. It wrote the Markdown file and nothing else. I had to ask for the JSON, and later it updated the JSON only when I reminded it.
The convention was written down, so why did the agent ignore it? The description lived in a documentation page that the agents never loaded in a project. The entry files they do read mentioned the JSON once, in a conditional sentence. And the step "mark the TODO as completed" pointed at the agent's private task list, which competed with the JSON as the place for the truth.
I changed four things:
- A short section in every entry file (
AGENTS.md,CLAUDE.md,copilot-instructions.md): list the plans at the start of a session, read the ones with open work, and update the JSON in the same commit as the work. - The full protocol as an instruction file that loads whenever an agent touches a plan.
- Concrete triggers. Use the paired plan when the request mentions phases, when the work touches more than one repository, or when it lands in more than one pull request. The agent says which trigger applied, so I can correct it.
- A validation command for the JSON.
The lesson: a rule that is not where the agent reads is not a rule.
The second gap: stopping in the middle
The first version also handled one case badly: an agent that stops in the middle of a task. It left the task in-progress and nothing else. No next action, no branch, no last commit, no list of uncommitted work. A task could be marked completed without anyone able to check it, and nothing said who had a task, so two agents could take the same one.
I added the fields from the example above, all optional, so the plans I had already written still validate. I also wrote a prompt for the agent that picks a plan up. Its first move is to check the handoff against git: the branch, the last commit and the uncommitted files. If something does not match, it stops and asks. A handoff written two sessions ago and never updated is worse than none, because the next agent believes it.
What it does not do
It is a convention, and I want to be clear about where it stops.
- Nothing enforces the claim rules. An agent can ignore a claim. The rule says a claim older than 24 hours can be taken over when the task has no log entry from the last 24 hours. That relies on the timestamp the claiming agent wrote.
- The validator warns, it does not block. Schema errors fail. The softer checks print warnings, and only strict mode turns them into a failure.
- It does not make the agents smarter. An agent can write a bad plan, and the JSON will record it faithfully.
I put what I can in a check and leave the rest as a written rule, and I try to be honest about which is which.
Use it
The skill is called paired-plan. It has the rules for creating, resuming, claiming and handing off a plan, with notes for several agents and for subagents. It is on this site, under the MIT license:
SKILL.md: the skill, and the full description of the convention.plan-state.v1.schema.json: the JSON schema. Point$schemain your plan at this address.validate-plan.mjs: a validator that uses only Node. It reads the schema in thereferencesfolder next to it.example-plan.jsonandexample-plan.md: a plan that passes--strict.
Start in five steps:
- Download the files above and keep the folder layout of the skill. Put the folder where your agent loads skills. For an agent that does not load skills, point its entry file at
SKILL.md. - Add the "Plan State" block from
SKILL.mdto the entry file of every agent you use (AGENTS.md,CLAUDE.md,.github/copilot-instructions.md). - Ask your agent to "create a plan" for a task that needs more than one session. It should say which trigger applied, and write both files.
- Run
node scripts/validate-plan.mjsbefore each commit. - Test the handoff on purpose, before you need it. Stop a session in the middle of a task, open another agent, and say "resume the plan". See what it asks, and fix the plan, not the prompt.
This is what the validator says when a plan breaks the soft rules:
docs/export-retry-plan.json: warning: retry/write-with-retry is completed but work item retry has no log entry
docs/export-retry-plan.json: warning: retry/use-in-export is in-progress but has no claimedBy
docs/export-retry-plan.json: warning: multi-session plan has an in-progress or blocked item but no handoff
docs/export-retry-plan.json: valid
The plan is valid, because the schema passes, and it still has three warnings. With --strict, the same run exits with an error.
How it was made, and what I did not check
I built this by talking to my agents. I told Claude what I wanted, a plan that several agents can work on over several sessions, and I asked it to help me design a schema for that workflow. The first proposal for the two files came from the Copilot coding agent, as a pull request with my commits as co-author. I iterated on that proposal with the agents until I had the schema and documentation I use today. The Copilot coding agent wrote the first draft, not me, and I want that to be clear.
I did not research what already exists. I did not read any guide or specification before or while I built this, and I did not compare it with other approaches. I cannot tell you how it compares, and it is likely that other people solved the same problem in similar ways. If you know some, I would like to read about them.
There is one outside thing I do remember. I watched a video by Pelado Nerd, ACP en la vida real - Conecté 2 agentes para delegar trabajo. It is about ACP, an open protocol for communication between a client and a coding agent, which can carry sessions and progress, not only text. I do not remember much more, and I was not trying to build an ACP. I kept one idea: different agents need a way to keep the same context. I wanted a skill to write plans that live across sessions and across agents, so I do not depend on the token limit of one agent, and another agent continues where the first one stopped. My answer is not a protocol between agents. It is two files that all of them can read.
What this was checked against: the failures I describe here, and the validator.
What I took from it
"Done" needs a way to check it. A task with no acceptance check is only a claim by whoever marked it.
Check a handoff against the real state. The branch, the last commit and the uncommitted files tell you in seconds whether a note can be trusted.
Put what you can in a check, and say what you cannot. The schema and the warnings are checks. The claim and takeover rules are not.
State belongs in files, not in a conversation. The context window of one agent is the worst place to keep what several agents need to know.
Do not depend on one platform. Limits, prices and models change. A plan in two plain files works the same with Claude, Codex, Copilot or a model on my own machine.
Try it, and tell me how you do it
This is a proposal from one person who works alone with a few agents. It is not finished, and I want to learn from other people's use of it. If you try it, I would like to hear:
- how you hand work from one agent to another today, and what breaks;
- what went wrong when you used the plan files, and which rule was missing or unclear;
- whether it worked with another agent, another model or a local model, and which one;
- how you work with subagents, and who writes the plan when several run at the same time.
You can download the skill, the schema and the validator from this site. Write to me at me@miguelcolmenares.com with your experience, a question, or a plan file that did not work. I read everything, I will update the skill and this article with what I learn, and I will name who helped.