✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

✨ Add invocation-scoped Devin sessions to xmd run (#740) - #742

Draft
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions
Draft

✨ Add invocation-scoped Devin sessions to xmd run (#740)#742
taras wants to merge 6 commits into
mainfrom
agent/issue-740-devin-sessions

Conversation

@taras

@tarastaras commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Why

xmd run --default-agent devin did not work, and could not be made to work
without deciding something first. ACPX's registry has no Devin entry, so the
name fell through to the bare command devin — Devin's interactive CLI, which
speaks no protocol. The command that does is devin acp.

Behind that is the real question. Every agent this provider serves is durable:
its conversation outlives the invocation, and a later run continues the one its
record names. Devin's ACP surface publishes no acceptance event and no
provider-native conversation identity — established by the sanitized ACP traces
linked from #740 — so nothing about one of its sessions can be written down and
continued later. Making it durable anyway would
have meant weakening #648's materialization contract for Codex and Claude too.

Closes#740.

What changes

Before, with devin on PATH:

$ xmd run review.md --default-agent devin2 agent prompt(s) failed

After, the same document keeps one live Devin conversation for the length of the
run:

<Agentname="devin">
<Sessionname="review">
<Promptas="first">Read the diff and say what changed.</Prompt>
<Promptas="second">Given what you just said, what should I check?</Prompt>
</Session>
</Agent>

The second Prompt reaches the conversation the first started. A different
<Session> gets its own live conversation and may run beside it. When the run
ends, nothing about either is retained — the next invocation starts fresh.

Four surfaces refuse Devin instead, each before it is contacted:

SurfaceRefusal
<Session.Launch>unsupported-capability — there is no durable identity for a native UI to resume
xmd workflowagent resolution fails; a run continues its conversation across executions, including on a partial continuation whose next live operation is a Prompt
xmd planthe existing no-Agent-context refusal; --session does not change it
<Plan> in an ordinary runthe same refusal, from the same settled context; <Plan session> does not change it

Every other agent is unchanged: same registry answer, same materialization, same
retention, same ownership, same native behavior.

How it works

resolved agent name → host declares the session lifetime → durable path unchanged
↘ invocation path: private
store, own runtime, live
handle, nothing retained

The lifetime is a host decision, not an inference. AcpxProviderDependencies
gains sessionLifetime(agentName): Result<AcpxSessionLifetime>, answering
"durable" or "invocation". The provider asks it with the resolved agent name
before it prepares an adapter, probes availability, reads a store, places a
session or starts any child; an absent dependency is Ok("durable"), which is
what every existing host and agent already was. An Err refuses the operation
there — which is how the workflow profile gets its refusal ahead of any contact.
Nothing infers a lifetime from a command string, adapter metadata, a record, a
title, or anything an agent answered.

For an invocation-scoped session the provider owns one private in-memory ACPX
store, keyed as ACPX's own file store is and reachable from nothing else, plus a
separate unbound runtime built on it — because a runtime carries one store, so
sharing one would share every record either lifetime wrote. Placement, the
ensure, later record reads, the permission-routing refresh and teardown all use
the store and runtime the session was placed with.

A fresh Devin <Session> is inert. The first subscribed <Prompt> probes
availability — the probe moves here precisely so that native launch, workflow
and Plan can refuse before Devin is spawned — then ensures in persistent mode
withoutmaterialization: "first-turn-acceptance", waits on no acceptance
Devin does not publish, and keeps that handle until provider teardown.

Review guide

Start with:AcpxProviderDependencies.sessionLifetime in
packages/acp/src/provider.ts, and the §Session lifetime section in
specs/acp-client-spec.md.

Then review:

  1. packages/cli/src/agent-stack.ts — the devin acp overlay and the two host
    declarations, which is where the product decision lives.
  2. packages/acp/src/provider.tscreateInvocationStore, storeFor,
    lifetime-keyed unbound, and the lifetime === "invocation" branches in
    promptStream, resolveSession and launch.
  3. packages/cli/src/{workflow-agent,authorship-profile}.ts — the two host
    refusals.
  4. The tests and the six specification updates.

Look carefully at:

  • ManagedState gaining "live-invocation", and placementStateOf(), which is
    what keeps pending/established meaning exactly what they meant before.
  • ensureThrough() refusing an invocation-scoped bound build rather than
    falling back — a build binding is retained history, and this session has none.
  • Teardown: an invocation handle is an ordinary owned handle, so the existing
    all-settled ledger closes it.

What must stay true

  • The durable path is byte-for-byte unchanged in behavior. Enforced by
    routing every difference through one lifetime value carried on the prepared
    and managed entry; checked by the whole pre-existing Tier AP/SM/WAP suite and
    by AI5, which watches a mixed run and asserts Codex still materializes through
    first-turn acceptance and still writes to the durable store.
  • No load or save crosses lifetimes. Enforced by storeFor() and the
    lifetime-keyed runtime map; checked by AI4 (a watching durable store that
    records every key it is asked about, and is asked none) and AI5.
  • A durable-only surface refuses before contact. Enforced by asking
    sessionLifetime first in resolveAgent() and at the top of launch();
    checked by AI6, WAL17, C14, PC27 and PC28, each asserting the doctor, ensure,
    turn and native-process counters at zero.
  • A Session's lifetime is compatibility, not a property of its placement.
    Two agent names can resolve to one command and be declared with opposite
    lifetimes, because ACPX resolves an unknown name to the name itself and a
    lifetime is never inferred from a command. Enforced by comparing the
    consuming agent's declared lifetime against the Session's retained one before
    the resolved command, as an independent third guard; checked by AI8 and AI9
    in both directions with every store, route, ensure, turn and establishment
    observation snapshotted and unchanged, by DV3 through the production
    <Agent name="devin acp"> / <Prompt agent="devin"> shape, and by AI10,
    which crosses the alias under one declared lifetime and shows the Session
    placed through one name and consumed through the other still reaches one
    ensure and one live handle.
  • Nothing Devin reports becomes durable identity. Enforced by not adding a
    Devin checkpoint namespace and by skipping route publication and
    sessions.established(); checked by AI7, which supplies an ACPX record id, a
    backend session id and cognition.ai/userMessageId and asserts none of them
    is written anywhere.

How to verify it

deno task test packages/acp/tests/provider.test.ts
deno task test packages/cli/tests/agent-adapters.test.ts
deno task test packages/cli/tests/workflow-agent.test.ts
deno task test packages/cli/tests/plan-cli.test.ts
deno task test packages/cli/tests/plan-component.test.ts
deno task test packages/cli/tests/devin-agent.test.ts

Every new case was verified by breaking the implementation and watching it fail,
not only by watching it pass:

  • AI1/AI4/AI5/AI6/AI7 fail when lifetimeFor is forced to answer durable.
  • AI3 fails when teardown stops at the first failed close — it proves
    cancellation reaches the halted turn only, and that a failing close does not
    prevent the remaining handles from being attempted.
  • AI2 fails when invocation placement ignores the session name.
  • WAL17 fails when the workflow profile's sessionLifetime is removed. It
    covers a first use and a partial continuation whose next live operation is
    the Prompt, journaling nothing new.
  • PS11/PC27/PC28/AE10 fail when invocationScopedAgent is neutralized.
  • AI8/AI9/DV3 fail when the Session lifetime guard is removed — the defect
    they pin needs no test seam, since a document naming the raw devin acp for
    its <Session> and devin for its <Prompt> reaches it today.
  • AI10/AI8 fail when a Session is instead pinned to the agent name that
    placed it. That is the opposite defect, and the pair is what shows the guard
    refuses disagreement about the lifetime rather than the alias itself.
  • DV1/DV2 fail when devin resolves to the bare command, and again when
    storeFor always answers with the durable store.

packages/cli/tests/devin-agent.test.ts is the black-box half: it puts a real
ACP agent named devin on a child's PATH, refuses any argv other than acp,
and runs the production CLI against it. That is the only way to reach the
command string and ACPX's own Windsurf compatibility shim — it asserts the
Windsurf clientInfo, the cognition.ai/requestDiagnostics capability, the
{} diagnostics reply, a quietly ignored vendor notification, two Prompts
reaching one live child, and a second invocation reaching a fresh one. No
credential, no network, no model turn.

Scope

Included

  • devindevin acp on the xmd run and xmd plan stacks.
  • The host-declared session lifetime, and the provider's invocation-scoped path.
  • Refusals in native launch, the workflow profile and Plan authorship.
  • The credential-free black-box test.
  • architecture.md and the five specifications the boundary touches.

Intentionally unchanged

  • Durable Devin sessions, transcript injection, provider state export, workflow
    artifact portability, conversation forking, native Devin launch and model
    selection. None is buildable on what Devin's ACP surface publishes today.
  • packages/acp/vendor/acpx — the Devin shim already exists there; this adds no
    patch and no dependency merely to name a command.
  • Certifying a live agent against a real backend. That is a provider-neutral
    agent-ACP capability to be designed later, so no Devin-specific discovery or
    certification document ships here; the sanitized traces linked from Add Devin Agent sessions to xmd run #740
    remain the discovery evidence.
  • packages/acp/src/checkpoint.tscognition.ai/userMessageId stays
    unrecognized beside the explicit Codex and Claude namespaces.
  • The five discovery commits underneath this one are preserved as the
    prerequisite history rather than squashed.

New abstractions

  • AcpxSessionLifetime and sessionLifetime exist because three hosts need to
    give three different answers about one agent — xmd run serves it, xmd workflow cannot, and Plan authorship cannot — and a provider that guessed
    from the command would be inferring the one thing that must be declared.
  • ManagedState's "live-invocation" exists so pending and established
    keep their durable meanings; reusing established would have made a session
    with no durable identity report that it had one.
  • Each new abstraction has multiple concrete uses or a clear justification.
  • No speculative functionality is included.

Risks and limitations

  • The real provider is not qualified by this PR, and nothing here claims to
    be.
    Every assertion is about this host's decisions and the wire the vendored
    ACPX shim speaks; none depends on a Devin account. That Devin itself holds a
    conversation across two turns and honours ACP cancellation is unproven, and
    proving it waits on a provider-neutral agent-ACP certification capability that
    does not exist yet.
  • A missed hard-coded store read would silently cross lifetimes. The mitigation
    is that lifetime is carried on the prepared and managed entry and every read
    goes through storeFor(); AI4 and AI5 are what would catch a regression.
  • packages/cli/tests/devin-agent.test.ts does not run under Node or Bun: it
    spawns the Deno source entrypoint and a Deno-module ACP agent, an argument
    vector only the Deno CLI understands. Recorded in
    scripts/runtime-test-exclusions.ts; the provider decisions it fences are
    proved portably by Tier AI.
  • This PR carries the five Devin discovery commits as prerequisite history. The
    documents those commits added are deleted again on top, so they cancel out and
    appear in no file in the net diff — read the commits, not just the diff, to see
    that history. git diff d4192c9a..HEAD is the implementation plus that
    deletion.
  • It has been rebased onto d820e8f8, which includes ✂️ Make xmd plan produce approved XMD source only (#724) #749 ("Make xmd plan
    produce approved XMD source only"). That removed xmd plan --run and rewrote
    plan-cli.test.ts, so this PR's Plan-refusal case was rewritten against the
    new surface as PS11, and the Plan acceptance row is PS13 in both
    specifications. Reviewers who saw the earlier SHAs should re-read those two
    places.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown

PR #742: ✨ Add invocation-scoped Devin sessions to xmd run (#740)

19 files, +1954 / -72

Scope

🔴 PR has 2026 lines changed. Split into focused PRs.

🟡 2026 lines changed. PRs under 400 receive more thorough review.

🟡 Changes span 9 directories.

Structural

Oxlint structural signals:

  • no-unused-vars ×5: packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
  • no-empty-function ×2: packages/acp/src/provider.ts

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 27 diagnostics across 2 files (7 rules)
Density: 0.014 violations/added-line

consistent-function-scoping (11): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-unused-vars (5): packages/cli/src/authorship-profile.ts, packages/acp/src/provider.ts
no-shadow (3): packages/acp/src/provider.ts
no-useless-spread (3): packages/acp/src/provider.ts
no-empty-function (2): packages/acp/src/provider.ts
no-floating-promises (2): packages/acp/src/provider.ts
no-unsafe-type-assertion (1): packages/acp/src/provider.ts

Correctness

No extraneous code patterns detected.

An ordinary `xmd run --default-agent devin` starts exactly `devin acp`, which
is the command ACPX recognizes and answers as Windsurf. That agent's sessions
are declared invocation-scoped before any adapter is prepared or any
availability probe spawns anything, because Devin's ACP surface publishes no
acceptance event and no provider-native conversation identity, so nothing about
one of its sessions can be written down and continued later.
The provider owns one private in-memory ACPX store for that lifetime and a
separate unbound runtime built on it, so no load or save crosses between
lifetimes. A fresh Devin <Session> is inert; the first subscribed Prompt probes,
ensures in persistent mode without first-turn acceptance, and keeps that handle
until teardown, so a second Prompt continues the same conversation while a
different Session runs beside it. Nothing it reports becomes durable identity.
Lifetime is part of a provider-issued Session's compatibility, compared before
the resolved command and independently of it: two agent names can resolve to one
command and be declared with opposite lifetimes, so consuming a Session through
a disagreeing lifetime is refused before any further store access, route,
ensure, turn or retention.
Native launch, a workflow attachment including a partial continuation, and Plan
authorship on both its surfaces refuse Devin before it is contacted. Every other
agent keeps its existing registry, materialization, retention, ownership and
native behavior.
@taras
tarasforce-pushed the agent/issue-740-devin-sessions branch from df6f4b9 to 60b94f8CompareSeptember 3, 2026 12:22
Sign up for freeto 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.

Add Devin Agent sessions to xmd run

1 participant

@taras