Skip to content

feat: add a run-once reservation mechanism to the workspace agent process API - #27369

Draft
ibetitsmike wants to merge 1 commit into
mainfrom
mike/codagt-757-lite/01-agent-start-tokens
Draft

ibetitsmike wants to merge 1 commit into
mainfrom
mike/codagt-757-lite/01-agent-start-tokens

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Stack context

Part 1 of the CODAGT-757 stack. Replaces the earlier "idempotent start tokens" version of this PR after review: the reservation rule now lives in its own mechanism rather than inside process management.

Why

A chat that resumes after a crash replays tool calls history still shows as unresolved. For execute that means running the shell command a second time. Preventing it needs a rule the agent can apply to a repeated request: perform the operation once, and answer a repeat with what the first attempt produced.

That rule is not specific to starting processes. edit_files is the clearest second candidate: replaying it is actively harmful, because the text it searched for has already been replaced, so the retry either fails or matches a changed file differently.

What

agent/agentrunonce holds the rule and nothing else:

  • reserve a key before doing any work, so a concurrent caller waits instead of repeating it;
  • attach later callers presenting the same input fingerprint to the value that was published;
  • refuse a key reused with a different fingerprint;
  • release a reservation whose operation never got underway, so a waiter takes it over;
  • retain completed reservations for a while, then evict them.

The registry is generic over both the key and the published value, and never interprets either. That is what lets operations with unlike results share the rule: this PR's adapter publishes a handle to a live process that the process manager keeps managing, while a future edit_files adapter would publish a plain response value. Both fake adapters are covered in the registry's own tests.

Process starts are the only adapter here. The process manager still owns output buffers, exit state, signals, and git-watcher registration.

Reservations are scoped to the chat

Following review, the registry key is a composite of the chat and the idempotency key, so two chats that send the same key value get independent reservations instead of colliding. Chat is therefore no longer part of the fingerprint, and cross-chat reuse no longer produces a false conflict.

The fingerprint covers only what decides what gets spawned: command, requested workdir, env, and the background flag. Display intent and how long the caller waits are excluded, since neither changes the process.

Two behavioral notes:

  • Recording the fingerprint when the key is reserved means a mismatched retry is refused immediately, without first waiting out the concurrent start it could never attach to.
  • A reservation becomes attachable when the process spawns, but its retention starts when the process exits. Those are deliberately separate events; starting retention at publication would evict long-running processes mid-flight.

Machine-readable conflicts

Start conflicts carry a code, so callers do not classify them by matching error text:

Code Meaning
input_mismatch The key was used within the same chat with different parameters. Permanent; retrying cannot resolve it.
start_pending The start holding the key had not published its process yet. Unresolved; a retry may attach.

The code rides a route-specific ProcessConflictError rather than a new field on the global codersdk.Response, following the existing WatchError precedent. Agents that predate this never answered 409 here, so an uncoded conflict is reported as permanent: treating an unresolved start as permanent costs an error result, while the reverse risks running the command twice.

Review changes in this revision

  • Registry keys are {chatID, idempotencyKey} composites; the cross-chat conflict test became a cross-chat independence test.
  • ClientToken is now IdempotencyKey on the wire (idempotency_key) and in Go. No client_token reference existed anywhere at merge-base, so there is no compatibility cost.
  • ErrOwnerPending is now ErrPublicationPending, and Outcome.Owner is now Outcome.Ticket, so the API no longer uses "owner" for a concept the reviewer found unclear.
  • The unlock-then-wait section of Reserve moved into tryReserve plus waitForPublication, so no method unlocks a mutex it did not take.
  • process_internal_test.go was rewritten around shared helpers (newTestManager, startGate, startAsync, awaitStart), and the two close-before-spawn tests collapsed into one table.
  • Added comments were cut roughly in half and rewritten in plain English.

This PR was written and revised by Mux, an AI coding agent, operating on Mike's behalf.

@linear-code

linear-code Bot commented Jul 21, 2026

Copy link
Copy Markdown

CODAGT-757

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: d3be23b9ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/01-agent-start-tokens branch from d3be23b to c6f4fb5 Compare July 21, 2026 13:07
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: c6f4fb53f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread agent/agentproc/process_internal_test.go Outdated
Comment thread codersdk/workspacesdk/agentconn.go Outdated
Comment thread codersdk/workspacesdk/agentconn.go Outdated
Comment thread agent/agentproc/process.go Outdated
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/01-agent-start-tokens branch from c6f4fb5 to d9b87ba Compare July 21, 2026 22:40
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: d9b87ba130

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mafredri mafredri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the atomic reservation in this PR is the right core idea, but it is being added at the wrong level.

The reusable rule is not specific to process starts:

For selected mutating tool calls, the workspace agent should perform one operation for a given ID and return the same work or result when that ID is repeated.

This stack does not need to convert every tool. It should add that shared mechanism and use execute as its first and only operation.

For example, the agent could keep an in-memory record containing:

tool-call ID
operation name
accepted input
running or completed state
result

A new ID reserves the record before doing any work. Repeating the same ID and input returns the existing state or result. Reusing the ID with different input fails.

execute would use this mechanism to start one process and retain its process ID. The existing process manager would continue to own process output, exit state, and signals.

This split keeps the useful work in this PR, especially reservation before cmd.Start and concurrent callers waiting for the owner. It also avoids embedding the shared replay rule inside process management, where another mutating tool could not reuse it later.

The scope can remain small: build the shared mechanism, connect execute, and leave every other tool unchanged.

🤖 This review was automatically generated with Coder Agents.

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/01-agent-start-tokens branch 2 times, most recently from f3c8095 to 3dfed86 Compare July 29, 2026 13:55
@ibetitsmike ibetitsmike changed the title feat: add idempotent start tokens to the workspace agent process API feat: add a run-once reservation mechanism to the workspace agent process API Jul 29, 2026
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

Agreed, and rebuilt this PR around it. The reservation rule now lives in its own mechanism, agent/agentrunonce, with process starts as its only adapter.

I did diverge from the record shape you sketched in one way, and want to flag it rather than bury it. The record carries operation name, key, accepted input fingerprint, pending/published/completed state, and a result slot, but the registry is generic over that result and never interprets it. Forcing a common result type is where the abstraction would have broken: execute's result is a live resource (output buffer, exit state, original start time, the retained process object that makes 60-minute recovery work), while a future edit_files result is a plain response value. So the reservation rule is shared and the result contract stays per operation. The registry's tests cover both shapes with fake adapters, one publishing a live handle and one a plain value, so the reusability is exercised even though only execute is wired up.

agentproc keeps owning process output, exit state, signals, and git-watcher registration, and supplies only a key and an input fingerprint.

Two things worth a look:

  • The fingerprint is recorded when the key is reserved, so a mismatched retry is now refused immediately. Previously only a cross-chat retry failed fast while a same-chat mismatch waited out the owner first, then failed anyway.
  • A reservation becomes attachable at spawn but its retention starts at process exit. The registry models those as separate events, because starting retention at publication would evict long-running processes mid-flight.

Conflicts are machine-readable now, per your #27370 comment: input_mismatch and start_pending, on a route-specific error type rather than a new field on the global codersdk.Response.

One correction to the wording here: this comment says "tool-call ID", and a bare provider tool-call ID is not stable across regenerations. Your #27370 comment gets it right, and the composed key is what landed.

Reviewed and revised by Mux, an AI coding agent, operating on Mike's behalf.

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

Follow-up: a self-audit of this branch found a duplicate-execution race in the reservation recovery path, now fixed.

A reservation can legitimately outlive the process it names for a moment: Reserve returns a published process ID, and a concurrent reap can evict that reservation and delete the process before the caller looks it up. The recovery path handled that by forgetting the reservation and reserving again. But it deleted by key alone, so this interleaving defeated run-once:

  1. A observes reservation value P1.
  2. A reap evicts that reservation and deletes P1.
  3. B reserves the same key and publishes P2.
  4. A finds P1 absent and forgets the key, deleting B's reservation.
  5. A reserves again and starts P3, so P2 and P3 both run.

Reservations now carry a generation, Reserve returns it, and Forget deletes only when it still matches. TestForgetIgnoresSupersededGeneration covers it; I verified it fails with the guard removed.

Also in this branch: dropped process.env, which no longer had a reader once the fingerprint replaced the field-by-field comparison, and was retaining request environment values for the full token retention window. Plus Outcome.Attached removed as derivable from Owner == nil, and several comment corrections.

Reviewed and revised by Mux, an AI coding agent, operating on Mike's behalf.

@ibetitsmike
ibetitsmike requested a review from mafredri July 29, 2026 15:48
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/01-agent-start-tokens branch from 6c58dd6 to 7bedd8a Compare July 29, 2026 15:52
Comment thread agent/agentproc/api_test.go Outdated
requireConflictCode(t, w2, workspacesdk.ProcessConflictInputMismatch)
})

t.Run("SameTokenDifferentChatsConflicts", func(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this conflict? Don't see a reason for two different chats to operate in the same "space", which would inherently allow this case.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and this test now asserts the opposite. Your composite-key suggestion below made the conflict unnecessary: reservations are keyed by {chatID, idempotencyKey}, so two chats using the same key value each get their own reservation.

SameKeyDifferentChatsStartIndependently now starts under chat A and chat B with the same key and asserts two distinct process IDs. TestSameKeyInDifferentChatsStartsIndependently covers the same thing at the manager level while chat A's start is still pending, which was the case that used to fail fast with a mismatch.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread agent/agentproc/process.go Outdated
// started under an idempotency token. It bounds how long the
// token keeps deduplicating starts (and serving the exited
// result) after the process exits.
tokenedProcessReapAge = 60 * time.Minute

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking, this does not prevent collection process results after, say, 2 hours? I could very well see a situation where a process is started and then go on to wait 2 hours for a sub-agent to produce results, only to then want to know exit status and output.

Just thinking out loud, but rather than storing N entries for 60 minutes, how about just storing the last N tokens/results for one specific chat, irrespective of time?

Time based constructs are always tricky and can result in unexpected bugs or issues.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collection after 2 hours already works, and I should have said so in the comment rather than leaving you to check. Retention starts when the process exits, not when it starts (m.runOnce.Complete is called from the cmd.Wait goroutine). A process still running after two hours is never reaped, so the sub-agent case you describe returns the real exit status and output.

The window bounds how long an exited result stays retrievable. For reference, the pre-existing untokened reap age is 5 minutes, so this stack already extends result retention 12x for keyed processes.

On last-N-per-chat instead of time: I do not think it is smaller. A per-chat count is unbounded across unbounded chats, so it needs a global bound too, and then an eviction order across chats. That turns into a process-history retention policy rather than a swap of one rule for another. I would rather keep the simple rule here and do the count-based cache as its own change if you want it. Happy to file it.

I did tighten the comment to state the contract instead of implying a general cap.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread agent/agentproc/process.go Outdated
// The workdir is taken as requested rather than resolved, because the
// default directory can resolve differently across retries (before
// and after the agent manifest loads), and an identical retry must
// attach instead of conflicting.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the logic of using request workdir is sound, but the comment is a bit out there. Technically it shouldn't be possible to reach here before manifest has been loaded.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped the manifest claim. You are right that it should not be reachable, and I could not construct a case where it is, so asserting it was speculation.

The comment now says only what is verifiable: the workdir is taken as requested rather than resolved, so an identical retry attaches even when the default directory resolves differently.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread agent/agentproc/process.go Outdated
// agentrunonce.ErrInputMismatch.
//
// ctx bounds only the wait for a concurrent start that owns the
// same token; the spawned process itself is never tied to it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to see this re-written in English. This way of writing is very LLM, very metaphorical.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewritten:

The context only limits how long a duplicate start waits for the concurrent start holding the same key. It does not cancel the spawned process.

I also did a pass over every comment this stack adds, not just the ones you flagged. Added comments in the four core files went from 44-53% comment:code down to roughly 30-40%, against a repo norm near 20% in those same files. Details in the summary comment.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread agent/agentproc/process.go Outdated

// The registry is keyed by the bare client token; chat identity
// is part of the fingerprint, so cross-chat reuse of a token
// conflicts instead of attaching.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is misplaced. Don't comment about fingerprint behavior on top of token, leads to misunderstandings.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted rather than moved. The composite key removed the behavior it described: chat is no longer part of the fingerprint, so there is nothing about cross-chat fingerprint matching to explain here.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread agent/agentrunonce/registry.go Outdated
done chan struct{}
// generation distinguishes successive reservations of one key, so
// a caller acting on a stale observation cannot disturb the
// reservation that replaced it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metaphorical, not comprehensible

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewritten:

Forget uses it to avoid deleting a replacement reservation.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread agent/agentrunonce/registry.go Outdated
Comment on lines +106 to +141
r.mu.Lock()
existing, ok := r.entries[key]
if !ok {
if r.closed {
r.mu.Unlock()
return zero, ErrClosed
}
// Insert the reservation before returning so a
// concurrent caller waits for this owner rather than
// performing the operation a second time.
r.generation++
reserved := &entry[V]{
fingerprint: fingerprint,
done: make(chan struct{}),
generation: r.generation,
}
r.entries[key] = reserved
r.mu.Unlock()
return Outcome[V]{
Owner: &Ticket[V]{registry: r, key: key, entry: reserved},
Generation: reserved.generation,
}, nil
}
// The fingerprint is recorded when the key is reserved, so a
// mismatched input is rejected without first waiting out an
// owner it could never attach to.
if existing.fingerprint != fingerprint {
r.mu.Unlock()
return zero, ErrInputMismatch
}
if existing.published {
outcome := Outcome[V]{Value: existing.value, Generation: existing.generation}
r.mu.Unlock()
return outcome, nil
}
r.mu.Unlock()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This belongs in a method to avoid the risky mutex handling.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Reserve is now a loop over two methods that each own their locking:

  • tryReserve takes the lock with defer Unlock and returns either the resolved outcome or the pending entry to wait on;
  • waitForPublication does the blocking select and is documented as requiring the lock not be held.

No method unlocks a mutex it did not take.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread agent/agentproc/process.go Outdated
// request attaches to the process the first request spawned
// instead of spawning a duplicate. It holds process IDs; the
// manager remains the owner of process state itself.
runOnce *agentrunonce.Registry[string]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion:

type runonceKey struct { chatID uuid.UUID; token string; }

This would avoid risking cross-chat pollution and does not conflict with same-token different-chat.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking this. It is better than both my fail-closed conflict and the earlier "just return the existing process" suggestion, and it cascaded further than I expected:

This reverses my position from the last round, where I argued chat belonged in the fingerprint. You found a third option that keeps the isolation without the false conflict.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread codersdk/workspacesdk/agentconn.go Outdated
// ClientToken makes the start idempotent: a repeated token
// attaches to the process it already started. Reuse with
// different parameters is refused.
ClientToken string `json:"client_token,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This token is all about idempotency, but is then named something generic like "client" that does not IMO convey what it is or what it does.

I'd like to see a better name for this. It doesn't necessarily have to be anything about idempotency, but something better than ClientToken that suggest you can use the same value multiple times.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to IdempotencyKey, including the JSON tag (client_token -> idempotency_key) and the derived names throughout the stack (tokenKey -> runOnceKey, tokenedProcessReapAge -> keyedProcessReapAge, SignalProcessByToken -> SignalProcessByIdempotencyKey).

No compatibility cost: git grep client_token at the merge-base returns nothing, so nothing outside this stack ever saw the old name.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

Comment thread codersdk/workspacesdk/agentconn.go Outdated
// ProcessConflictCode identifies why a start request lost to an
// existing client-token reservation. It travels in the response body
// so callers can branch on the kind of conflict without matching
// error text.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"travels in the response body" 😔. Honestly starting to space on these comments now so won't review them further down but I have no doubts about them being poor 😂.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped that phrase; the doc comment now states only the branching purpose.

I took the wider point too, since you stopped flagging partway through and never got to #27370 or #27372. I measured the whole stack rather than fixing the eleven you named: added comments were 44-53% of added code in the four core files, against roughly 20% in the same files at base. I went through every added comment against a keep-only-if-it-explains-a-non-obvious-invariant rule. The four core files are now roughly 30-40%, and the same pass covered #27370 and #27372. Summary comment has the per-file numbers.

Reply written by Mux, an AI coding agent, operating on Mike's behalf.

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/01-agent-start-tokens branch 2 times, most recently from 121599a to 3f1ac82 Compare August 1, 2026 04:01
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

Revised. Your composite-key suggestion turned out to be the largest change here, and it cascaded into #27370 and #27372 as well.

Design

Registry keys are now {chatID, idempotencyKey} composites, so two chats reusing a key value get independent reservations. Chat left the fingerprint, cross-chat reuse no longer conflicts, and the redundant chat check in #27372's by-key signal path is gone. The cross-chat conflict test is now a cross-chat independence test. This reverses my position from the last round; you found a third option that keeps isolation without the false conflict.

Naming and structure

ClientToken -> IdempotencyKey (wire tag idempotency_key), ErrOwnerPending -> ErrPublicationPending, Outcome.Owner -> Outcome.Ticket. The unlock-then-wait section of Reserve split into tryReserve plus waitForPublication, so no method unlocks a mutex it did not take. process_internal_test.go now shares newTestManager, a startGate helper, and startAsync/awaitStart.

Comments

You stopped flagging partway through PR1 and never reached #27370 or #27372, so I measured the whole stack instead of fixing only the eleven you named. Added comments were 44-53% of added code in the four core files, against roughly 20% in the same files at base:

File Before After
agent/agentproc/process.go 48% 35%
agent/agentrunonce/registry.go 48% 28%
codersdk/workspacesdk/agentconn.go 76% 51%
coderd/x/chatd/chattool/execute.go (#27370) 37% 26%

agentconn.go stays high because almost all of it is exported SDK types and error codes, where the remaining lines are doc comments rather than narration. I also removed several claims that were not verifiable, including the manifest-loading speculation you flagged and a fingerprint comment that described fields the request does not have.

One pushback

I kept the 60-minute retention rather than switching to last-N-per-chat, reasoning in the thread. Short version: retention starts at process exit, so your 2-hour case already works, and a per-chat count needs a global bound plus a cross-chat eviction order to be safe. Happy to do that as its own change if you still want it.

Revised by Mux, an AI coding agent, operating on Mike's behalf.

@github-actions github-actions Bot added the stale This issue is like stale bread. label Aug 9, 2026
@github-actions github-actions Bot closed this Aug 14, 2026
@ibetitsmike ibetitsmike reopened this Aug 27, 2026
…cess API

Adds agent/agentrunonce, a keyed reservation registry that makes a
repeated operation run once: the first caller to reserve a key performs
the work, later callers presenting the same input attach to the value
it published. The registry never interprets that value, so operations
whose results have nothing in common can share the rule. Process
starts are its first adapter; the process manager keeps owning output,
exit state, signals, and git-watcher registration.

Process starts pass a client token as the key and a digest of the
fields that decide what gets spawned (command, requested workdir, env,
background, chat) as the input fingerprint. Recording the fingerprint
when the key is reserved lets a mismatched retry fail immediately
rather than first waiting out a start it could never attach to.

Start conflicts now carry a machine-readable code so callers do not
have to match error text: input_mismatch is permanent, start_pending
means the owning start had not published a process yet and a retry may
still attach.
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-757-lite/01-agent-start-tokens branch from 3f1ac82 to 22dc279 Compare August 27, 2026 14:41
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@mafredri The stale bot closed all four PRs in this stack while they sat idle; they are reopened, and the whole stack is now rebased onto current main (about 520 commits of drift).

Rebase notes:

  • chattool/execute.go and generation_preparer.go collided with the agent-browser-session work from main. Both resolutions are plain unions: ExecuteOptions now carries AgentBrowserSession and Logger, and the env plumbing from main is untouched.
  • Part of this stack's dispatcher refactor had independently landed on main (executeTool taking options), so the rebased commits shrank slightly. Each rebased commit was diffed against its original to confirm nothing else moved.
  • Unit tests for every touched package pass locally on the rebased heads; CI is running on all four PRs.

When you have time, another pass over the stack would be appreciated. All 31 of your earlier threads have replies, including the two where I pushed back with evidence and left the final call to you.

Xum acted on Mike's behalf (@ibetitsmike).

@ibetitsmike
ibetitsmike requested a review from mafredri August 27, 2026 14:44
@github-actions github-actions Bot removed the stale This issue is like stale bread. label Aug 28, 2026
@github-actions github-actions Bot added the stale This issue is like stale bread. label Sep 5, 2026
@github-actions github-actions Bot closed this Sep 8, 2026
@mafredri mafredri reopened this Sep 9, 2026
@github-actions github-actions Bot removed the stale This issue is like stale bread. label Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants