Skip to content

feat(spawn): honor the persona file's agent: harness pin - #911

Merged
davidfarah2003 merged 28 commits into
mainfrom
life/869-persona-agent-field
Aug 28, 2026
Merged

davidfarah2003 merged 28 commits into
mainfrom
life/869-persona-agent-field

Conversation

@davidfarah2003

@davidfarah2003 davidfarah2003 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

What

A persona file declaring agent: in its frontmatter is now honored when the spawn harness is chosen. The precedence is explicit --agent > persona agent: > invoking CLI COTAL_DEFAULT_AGENT > manager COTAL_DEFAULT_AGENT > product default. Fixes #869.

Why it was broken

The manager resolved its connector before loading the persona file, so the file could not participate in harness selection. AgentDef also had no modelled agent field, and the detached CLI collapsed an explicit flag and the environment default into one control-plane value.

The first fix separated the explicit flag but dropped the caller's default. That changed detached behavior when the invoking CLI set COTAL_DEFAULT_AGENT and the already-running manager did not. An unpinned persona then used the manager or product default instead of the caller's default.

The fix

  • core: AgentDef.agent is modelled. loadAgentFile parses it as a known field, and saveAgentFile round-trips it so persona redefinition preserves the pin.
  • CLI detached: spawnDetached sends agent and defaultAgent separately. The caller default keeps its default semantics without becoming an explicit override.
  • manager: startAgentActive loads the authoritative persona once before connector resolution. Harness precedence is opts.agent ?? def?.agent ?? opts.defaultAgent ?? defaultAgentType(DEFAULT_CONNECTOR).
  • manager endpoint contract: the closed spawn schema admits defaultAgent, and shared validation refuses an empty value.
  • CLI foreground: the selected harness is values.agent ?? def.agent ?? defaultAgentType("claude").
  • extension materialization: spawnRequiredExtensions remains root-free. Foreground connector materialization happens only after the authoritative target persona is loaded.
  • loud guard: an uninstalled or unregistered pinned connector fails with the connector installation diagnostic. There is no fallback to another harness.

Reachability proof

pnpm smoke:persona-agent exercises the real entry paths with a throwaway broker, a real manager, the kernel-parsed CLI, and a separate child CLI process.

All 14 assertions pass. They cover:

  • detached persona pin versus environment default
  • explicit flag precedence
  • an unpinned persona invoked with caller-only COTAL_DEFAULT_AGENT while the running manager lacks it
  • foreground persona pin precedence
  • a root-free pre-dispatch extension hook
  • divergent current-directory and selected-mesh persona roots
  • a loud published-binary refusal naming the selected mesh persona's pin

The caller-default cell is discriminating. On the previous public head it failed with no connector registered for "claude" while the caller requested other.

Mutation proof kills all 10 mutations in bin/smoke/mutations/persona-agent.json on their named assertions. The two caller-default mutations kill removal of the dedicated field and manager-before-caller mis-precedence on the same real detached cell.

Also green on the current-main descendant:

  • full workspace build
  • pnpm smoke:agent-file: 41 assertions
  • pnpm smoke:start-overrides: 34 assertions
  • pnpm smoke:manager-service-ops: 62 assertions
  • pnpm smoke:launch-parity
  • focused typechecks for core, workspace, CLI, manager, and connector-core
  • pnpm check:docsbundle
  • pnpm changeset status
  • pnpm check:shard-stability origin/main HEAD: the new smoke is appended at the true suite tail and moves zero pre-existing suites

The feature and gate surfaces were independently reviewed at exact head b46f09ff by four model families: cold DeepSeek APPROVE, contract GLM PASS, implementation GPT PASS, and tests Grok APPROVE. Final merge commit 972411c3 then integrates current main 81b4e1f; its only tree delta from the reviewed head is the .internal submodule pointer already present on main. Every reviewed product, test, documentation, package, and generated file remains byte-identical to b46f09ff.

Compatibility

  • Existing personas carrying agent: now use that pin. This is the intended behavior change.
  • Detached CLI callers keep their own COTAL_DEFAULT_AGENT even when the manager environment differs.
  • Persona pins and explicit flags retain higher precedence than either environment default.
  • defaultAgent is an imperative manager control field. It does not enter @cotal-ai/core, AgentCard, SPEC.md, or spec/cotal.schema.json.
  • Manifest and direct manager launches omit the field and keep their existing authority and defaults.
  • AgentCard.meta.connector remains derived from the live session and cannot be spoofed by the file.
  • Docs are updated in agent-files.md, connectors.md, cli.md, and config.md.
  • The changeset lists core, CLI, manager, and connector-core for the fixed-group release.

Closes #869

Cotal added 8 commits August 27, 2026 03:49
A persona declaring 'agent: jcode' was silently launched on whatever the
env or product default chose: the key swept into the verbatim meta bag
(no code path read it) and both launch paths resolved the connector
before loadAgentFile ran. The harness now resolves once on every spawn
path as: --agent flag > persona agent: > COTAL_DEFAULT_AGENT > default,
matching model/variant precedence.

- core: AgentDef.agent modelled in loadAgentFile/saveAgentFile
- cli: foreground consults def.agent; --detach threads only an explicit
  flag across the control plane (flag and env collapsed into one field
  before, making file precedence unreachable manager-side)
- manager: startAgentActive loads the persona before resolving the
  connector (single load, consumed downstream)
- spawnRequiredExtensions consults the pin for pre-materialization
- a pin naming an unregistered connector fails loud (no fallback)
- smokes: loader round-trip, manager precedence, and a reachability
  suite driving the real CLI/manager over a throwaway broker (#869)
Six mutations against the #869 fix, each naming the assertion it must
redden: the manager precedence line (restores the shipped env-beats-file
defect), the CLI control-plane collapse (pre-#869 flag/env merge), the
foreground drop, the requiredExtensions drop, the loader meta-sweep, and
the manager unit cell. Commands build the workspace packages first and
afterRestore rebuilds, because every graded suite resolves dist/.
…arker

The loader mutation's expectRed was a substring of the GREEN line, so the
grader correctly refused to count it (WRONG-RED). The manager-unit
mutation inherited the reachability suite's completion marker while
running start-overrides, which prints a different trailer; it now names
its own.
With agent: swept back into meta, the parse cell still passes (meta is
unread by it) while the meta-purity cell fails, so the proof names that
one. The unit suite's completion marker is its failure banner: under the
mutation it stops at the red cell and never prints the green trailer.
The agent-file smoke prints its summary on both pass and fail (with the
count), so the marker is the summary line, not a success-only trailer.
…frozen

The gate requires every ungated suite to carry a reason. Gating this one
means inserting into bin/smoke/ci-suites.txt, which is frozen by position
until PR #880: the shard walk is round-robin by index, so a mid-file
insert re-shards every later suite across CI runners. Declared in UNGATED
with that reason instead; one-line reversal when the freeze lifts.
The known-set mutation alone still parses def.agent (the return line is
independent), so a revert of the field to unmodelled status has two
layers: stop parsing it into the modelled field (kills the parse cell),
and drop it from the known-set so meta re-captures it (kills the
meta-purity cell). Plus the writer mutation: a saveAgentFile that stops
emitting the pin makes every redefine silently drop it, which is the
runtime redefine path (cotal_persona), not just first spawn.
check:docsbundle regenerates the bundle and diffs, so the docs edits ride
with their generated copy.
@davidfarah2003

Copy link
Copy Markdown
Contributor Author

CI note: the two red smoke shards fail in smoke:artifact-store (shard 0) and smoke:backup-inventory (shard 1) on assertions about endpoint stream inventory. The same two suites fail identically on the base commit's own CI run on main (ccd9946, run of 2026-08-26T14:50Z: shard 0 at smoke:artifact-store, shard 1 at smoke:backup-inventory), and this branch does not touch anything those suites read (the only core change is the AgentDef.agent frontmatter field; neither suite references it). These are pre-existing failures carried from the branch point, not regressions from this change. Everything else on this head is green: unit, docs (bundle regeneration check), live, Windows (all four shards plus soak), CodeQL, and smoke shards 2 and 3.

Cotal added 5 commits August 27, 2026 04:52
The repo rule bans them in text landing on GitHub; the changeset rides
into CHANGELOG.md verbatim, so it is public writing like the PR body.
Punctuation chosen per sentence, not blind-replaced; pre-existing style
elsewhere in these files is untouched.
Re-applies the changeset, agent-files, and connectors fixes lost from the
previous punctuation commit (only the cli.md edit survived it). Chosen
per sentence; pre-existing style elsewhere untouched.
The edit tool's write to this file did not persist twice; applied and
verified byte-level before committing.
@davidfarah2003

Copy link
Copy Markdown
Contributor Author

BLOCK

Graded head 14b6ce4ad98138270088b04f8b6c5c7ac6b64317, re-resolved as a commit immediately before posting.

Model line: GPT-5.5. I am independent of the authoring model line for this PR.

Blocking finding

implementations/cli/src/commands/spawn.ts:214-233 makes foreground extension pre-materialization a best-effort lookup. It catches every persona resolution or parse failure and selects COTAL_DEFAULT_AGENT or Claude instead. implementations/cli/src/command.ts:194-195 runs requiredExtensions before the spawn command body. If that fallback connector is unavailable or broken, materializeExtension throws before the authoritative target persona is loaded. A persona pinned to connector X can therefore fail because the preflight guessed connector Y.

This is foreground-only because --file and --detach return before this lookup. It still contradicts the repository's no-fallback rule and the changeset's unqualified claim that every spawn path follows flag, persona file, environment default, then product default. It can also silently install the wrong connector even when the eventual spawn would have selected the right one.

The reachability smoke covers pre-materialization only while running from the workspace root. It does not cover a foreground spawn from outside the target root with --space or an external --config, which is the failing shape admitted by the body.

What would change my mind

Make pre-materialization resolve the same target root and persona reference as the spawn body, failing loudly on resolution errors, or defer extension materialization until after the authoritative persona load. Add a foreground test from outside the target root using --space or an external --config, with the environment default naming a different connector.

Checked

  • PR body, exact diff, changeset, documentation, core loader and writer, foreground and detached CLI paths, manager precedence, extension materialization order, and focused smokes
  • Archived exact head built successfully with pnpm build
  • pnpm smoke:persona-agent: 9 of 9 checks passed
  • pnpm smoke:agent-file: 41 of 41 checks passed
  • pnpm smoke:start-overrides: all checks passed

Not checked

I did not run mutation-proof, aggregate suites, live suites, or any shared stack command. CI conclusions were not used as execution evidence.

@davidfarah2003

Copy link
Copy Markdown
Contributor Author

APPROVE at 14b6ce4

Reviewer model: Grok 4.6

I re-resolved this head independently. git cat-file reports a commit, gh pr view 911 headRefOid matches, origin pull/911/head matches, and an invented-tail object name is rejected. Relative to the earlier 6c938dc I had inspected, this tip adds punctuation-only edits on the lines this PR introduces, a changeset punctuation fix, and two docs-bundle regenerations.

What I checked, from git objects at this SHA (no checkout of this tip, and I did not run the docs-bundle generator in any working tree):

  • Harness precedence is still opts.agent ?? def?.agent ?? defaultAgentType(DEFAULT_CONNECTOR) after loadAgentFile. Detached CLI still sends only the explicit flag (agent: values.agent). Foreground is still values.agent ?? def.agent ?? defaultAgentType("claude"). A resolveConnector throw is returned as {ok:false, error} with no default.
  • A hostile agent: can only select an installed connector name or fail loud. It cannot name an arbitrary binary. Writers remain the operator on disk and definePersona, which was already host-launch authority via spawn plus launchOptions.
  • At this SHA, source docs/*.md bodies equal the committed bundle page bodies (38 of 38, no extra or missing slugs). SPEC.md, spec/cotal-lang.md, and spec/cotal.schema.json match the bundle. Version remains 0.33.1.
  • Lines this PR added, versus 6c938dc, contain no em-dash and no ellipsis character in agent-files, cli, connectors, or the changeset. Pre-existing punctuation elsewhere in those files is untouched, which is the stated intent.
  • The mutation fixture at the prior ancestor named 8 cells (parse, known-set/meta-purity, writer/redefine, plus the spawn-path cells). This tip does not change that fixture. Fixture inspected only.

What I did not:

  • I did not run smoke:persona-agent or mutation-proof. This working tree is pinned at another PR's head, and checkout is forbidden here.
  • I did not re-run the docs-bundle generator against this SHA. Bundle equality was checked by comparing git objects, not by writing a generated file.
  • I did not treat a GitHub Actions job conclusion as evidence that a suite ran.

Open finding: personas that already declare agent: will use that pin wherever COTAL_DEFAULT_AGENT disagreed. That is the fix. Measured against the running fleet at review time: zero pinned personas disagreed with the harness they were actually running, twenty agreed, ten were dormant, and every pin across those thirty was claude or jcode (both registered connectors), so none would hit the new loud throw.

Verdict: APPROVE.

@davidfarah2003

davidfarah2003 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by a fuller two-part version below, which separates my independent verdict from my
position after reviewing a further finding. The conclusion is unchanged.

BLOCK

Head graded: 14b6ce4ad98138270088b04f8b6c5c7ac6b64317, re-resolved just now with git cat-file -t (returns commit), a positive control on a known-good sha, and a negative control on an invented tail (fails as expected). This matches the PR's current headRefOid via the API at the same moment.

Model: claude (opus).

Named gap up front: my worktree sits at a commit adjacent to PR #908's head, not at this PR's head, and I could not check out this head to run its suite (checkout is outside what my seat may do). I read the full diff, the full mutation fixture, and the full new smoke test source (bin/smoke/persona-agent.smoke.ts) via git show against the exact head above rather than executing it. Everything below is read verification, not an execution result, except where I ran a suite against code paths this PR does not touch.

What checks out. AgentDef.agent is not in spec/cotal.schema.json or SPEC.md, both untouched by this PR, so it is correctly a persisted file field rather than a wire shape. The pre-PR loader's known-key set excludes agent, so an older reader sweeps a file's agent: scalar into meta harmlessly with no throw, confirming backward compatibility by construction. saveAgentFile round-trips the field (confirmed in the diff, and mirrored by a dedicated mutation cell). The new precedence, flag over persona file over COTAL_DEFAULT_AGENT over default, is genuinely parallel to the adjacent model/variant resolution in the same function in manager.ts, not a divergent shape. Four docs files state the same precedence in matching language. materializePersona in launch.ts never emits an agent: key into a manifest's transient persona file, and the manifest's own MeshLaunchAgent.agent field (required, not optional) is what launchAgentToStartOpts forwards, so the manifest path's authority is preserved by construction, as claimed.

The blocking finding. implementations/cli/src/commands/spawn.ts:221-233 (spawnRequiredExtensions) resolves the persona's agent: pin for pre-materialization using findCotalRoot(), a pure cwd walk with no knowledge of --space, --server, or the current mesh selected by cotal use. The actual spawn body, several dozen lines later at spawn.ts:376-377, resolves the same persona from target.root, which comes from resolveTargetOrExit and, per packages/workspace/src/mesh-target.ts:290-341, follows a five-tier precedence where --space, --server, and the current selection all win over the cwd walk. These two resolutions can name different roots for an ordinary, documented workflow: an operator who has run cotal use <space> from one directory and then runs cotal spawn <persona> from a different one gets a target.root from the registry while spawnRequiredExtensions still walks from cwd.

Why this is not academic. implementations/cli/src/command.ts:194-195 runs requiredExtensions and calls materializeExtension(ref) on each returned ref before cmd.run is ever invoked. materializeExtension (ext-loader.ts:182-186) checks the live registry first, but nothing else in the CLI boot path imports connector code into that registry ahead of time; I traced bin/run.ts (only base/manager/delivery/auth self-register, connectors are explicitly deferred) and seedBoot/reconcileSeededConnectors (only reconciles the on-disk extension manifest and npm install state, never imports into the process registry). So when the cwd-walked root disagrees with the real target root, spawnRequiredExtensions reads a different, possibly wrong, persona file (or none, or the wrong wrong-connector's), materializes the wrong connector package, and the real registry.resolve<Connector>("connector", agentType) at spawn.ts:429 throws no connector registered for "<correct-agent>" even though the correct connector was never given a chance to import. This is worse than the pre-#869 defect in one respect: the old bug ran the wrong harness silently; this path can abort the spawn outright with a message that never mentions the pin, the cwd mismatch, or the actual persona resolved.

The PR's own doc comment on spawnRequiredExtensions names a version of this ("a BEST-EFFORT read", "an unreadable file contributes no pin", "the pin only ever ADDS precision") but that framing covers the file being absent or malformed, not the file being a different, valid file read from the wrong root under a real, common precedence tier (current) that this exact function ignores. The repo's own no-fallback rule is at stake here in a subtle way: this is not a silent degrade, it is a loud failure, but it is a loud failure for the wrong reason, naming the wrong extension, on a path that used to succeed before this PR made pre-materialization persona-dependent at all. Before #869, spawnRequiredExtensions used v.agent ?? defaultAgentType("claude"), values with no root dependency, so this failure mode did not exist; it was introduced by this PR's own fix.

What would change my mind: making spawnRequiredExtensions consult the same root-resolution precedence the command body uses (or, more simply, skip the persona-file read entirely and materialize a superset, or fall back to the flag/env/default when --space/--server are present and cwd cannot be trusted, since those are exactly the cases the mesh target resolver treats specially), plus a mutation or smoke cell that exercises the divergence (spawn from a directory with no local .cotal marker, after cotal use selected a different registered space, with a persona pinning a connector not seeded by default). The existing reachability suite spawns everything from a single scratch workspace root with no registry entries and no cotal use selection in play, so cells A through D cannot see this: they never separate "the root the cwd walk finds" from "the root the target resolver picks," because in that suite's setup the two always agree.

I did run pnpm changeset status read only against my own tree (unaffected by this PR) to sanity check the fixed-group mechanism cited by this PR's changeset, and it reports minor across the fixed set as the changeset states; I have no separate finding against the changeset itself. Docs are updated across agent-files.md, cli.md, config.md, connectors.md in the same change, and no em-dash or ellipsis character appears on any added line I read, including the changeset.

@davidfarah2003

Copy link
Copy Markdown
Contributor Author

Posting in two parts per request: what I concluded independently before any disclosure, and my position now, including whether I re-derived or accepted the finding that changed my verdict.

Model: claude (opus).

PART 1: INDEPENDENT VERDICT, before disclosure

Head graded: 14b6ce4ad98138270088b04f8b6c5c7ac6b64317, resolved at the time with git cat-file -t (commit), a positive control on a known-good sha, and a negative control on an invented tail (failed as expected).

Verdict at that head: APPROVE. I found no defect on my own pass.

What I checked. AgentDef.agent is not in spec/cotal.schema.json or SPEC.md, both untouched by this PR, so it is correctly a persisted file field rather than a wire shape. The pre-PR loader's known-key set excluded agent, so an older reader swept a file's agent: scalar into meta harmlessly with no throw, which I confirmed by reading the loader as it stood before this change, establishing backward compatibility by construction rather than assuming it. saveAgentFile round-trips the field, confirmed in the diff and mirrored by a dedicated mutation cell. The new precedence, flag over persona file over COTAL_DEFAULT_AGENT over default, matched the adjacent model/variant resolution in the same function in manager.ts, which I read side by side rather than trusting the PR's own description of parallelism. Four docs files stated the same precedence in matching language. materializePersona in launch.ts never emits an agent: key into a manifest's transient persona file, and the manifest's own MeshLaunchAgent.agent field, required rather than optional, is what launchAgentToStartOpts forwards, so the manifest path's authority is preserved by construction, which I verified by reading the type definition and the write path rather than accepting the PR body's claim.

I read the new reachability suite (bin/smoke/persona-agent.smoke.ts) and the mutation fixture (bin/smoke/mutations/persona-agent.json) in full. Both are real: a throwaway broker, a real in-process manager, real kernel-parsed CLI spawn commands, and six mutation cells each targeting a named line with a named expected-red assertion. I did not find the divergence described in part 2 on this pass. I did not independently trace spawnRequiredExtensions's root resolution against the command body's target resolution at that time.

I ran pnpm changeset status read only against my own tree and it reported minor across the fixed set, consistent with this PR's changeset. Docs updated across four files in the same change. No em-dash or ellipsis on any added line I read, including the changeset.

PART 2: POSITION NOW

Head re-resolved just now: 14b6ce4ad98138270088b04f8b6c5c7ac6b64317, unchanged from part 1, confirmed as commit via git cat-file -t, positive and negative controls checked, matched against the PR's live headRefOid at the same moment.

Position: REVERSED, from APPROVE to BLOCK.

I was told the shape of a finding by another reviewer's report: a pre-materialization path that can abort a spawn before the correct persona loads. I did not accept that report on its authority. I opened spawn.ts, command.ts, ext-loader.ts, registry.ts, and mesh-target.ts myself and traced the mechanism line by line before writing anything, and I found more than what was described to me, not merely the same thing restated.

What I re-derived and confirmed directly: spawnRequiredExtensions (spawn.ts:221-233) resolves the persona's agent: pin using findCotalRoot(), a pure cwd walk that has no knowledge of --space, --server, or the current mesh set by cotal use. The actual spawn body, at spawn.ts:376-377, resolves the same persona via resolveTargetOrExit, and I read resolveMeshTarget in mesh-target.ts:290-341 myself to confirm its five-tier precedence: --space, --server, and current each win over the cwd walk before the function ever reaches the local-project case that matches findCotalRoot's own logic. These two resolutions can disagree for an ordinary workflow: cotal use <space> from one directory, cotal spawn <persona> from another, both documented commands.

I then independently confirmed the consequence, which was not handed to me: command.ts:194-195 calls materializeExtension(ref) on every ref requiredExtensions returns, before cmd.run. I read materializeExtension in ext-loader.ts and Registry.resolve in registry.ts myself: resolve has no fallback, it is a bare map lookup that throws on a miss. I then read bin/run.ts and confirmed the four connectors are explicitly deferred, never statically imported, and I read seedBoot/reconcileSeededConnectors and confirmed they only reconcile the on-disk extension manifest and installed npm packages, never the in-process registry. That chain, which I walked myself rather than accepting as given, is what proves materializeExtension at the pre-materialization step is the only import point before the real connector resolve at spawn.ts:429, and therefore that a wrong pre-materialized connector produces a hard failure naming the wrong extension, not a graceful no-op.

I also checked the existing reachability suite against this specific gap myself, which was not part of what I was told: its four cells spawn from a single scratch workspace root with no registry entries and no cotal use selection in play, so the cwd-walked root and the target-resolved root always agree in that setup. That is why 6 for 6 mutations killed and 9 for 9 suite checks green do not contradict this finding: the suite cannot see a divergence its own fixture never creates.

So: I was told where to look, and I re-derived the finding at the artifact rather than restating a description of it. The parts of part 1 that already checked out (schema, backward compatibility, precedence parallelism, the manifest path) still hold on this pass and are not affected by the block, which is scoped to one function.

What would change my mind: spawnRequiredExtensions consulting the same root precedence the command body uses, or skipping the persona-file read entirely and materializing a superset, or deferring to flag and environment whenever --space/--server are present since those are exactly the cases the target resolver treats specially, plus a mutation or smoke cell that separates the cwd-walked root from the target-resolved root, which no current cell does.

Cotal added 2 commits August 27, 2026 08:15
spawnRequiredExtensions read the persona via the cwd walk pre-parse to
pre-materialize the pinned connector. That introduced a root divergence
the pre-#869 hook never had: resolveMeshTarget ranks --space, --server,
and the selected current ABOVE the cwd walk, so a spawn issued from a
directory outside the target could pre-materialize the WRONG connector,
and materializeExtension throws on an uninstalled extension, hard-
aborting the command before the body loads the correct persona. The
registry then lacks the pinned connector and the operator is told the
harness they wanted is unregistered.

The hook is root-free again, exactly as before #869; the spawn body
materializes the chosen connector immediately after the authoritative
persona load and before registry.resolve. One resolver, one root.
Reachability cell E makes the roots actually differ (registry-named
space vs foreign cwd) and asserts the target persona's pin builds the
launch with no confusing abort.
Cell E enables installed-extensions mode to reproduce the published-
binary abort path. Manifest resolution reads globalConfigDir(), which
follows XDG_CONFIG_HOME rather than COTAL_HOME, so the cell points it at
a scratch dir with an empty manifest: the operator's real
extensions.json is never consulted, and an unknown connector throws
from the scratch manifest. Read-only in practice before, but a suite
that names the live path at all is the contamination class.
@davidfarah2003

davidfarah2003 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Note on timing: this verdict was produced across a provider availability outage affecting this lens's model. Any gaps or delay in the round reflect availability, not unresolved analysis.

Verdict: BLOCK (tests and evidence lens). My position reversed after being told of a defect on the pre-materialization path; the finding was then re-derived first-hand rather than accepted, and the reversal is on the re-derived mechanics, which are worse than the reported framing. This comment states both positions plainly.

PART 1. What I concluded independently, before any disclosure, at head 6c938dc (the brief's originally claimed head was stale; I re-resolved and graded the current one at that time):

  • APPROVE. smoke:persona-agent passed 9/9 from real kernel-parsed CLI spawn commands over a throwaway broker, cell A being the issue's exact repro (persona pin beats COTAL_DEFAULT_AGENT on the detached path, with which connector's buildLaunch ran recorded by name). That is genuine reachability, not hand-built inputs.
  • Mutation fixture re-run, not cited: 8/8 KILLED, each on its named assertion (fail-fast mark counts one below baseline). The two cells added after the earlier six (the loader return-line cell and the saveAgentFile writer cell) were inspected individually: their find targets are unique in source, and the earlier set's completeness claim was indeed wrong in exactly the way the PR now describes, which the PR body's stale "6 mutations" count no longer matches.
  • Verified at base ccd9946: manager.ts:3408 is the precedence line and :3495 the loadAgentFile call; :3266 is inside opModels, so the issue's cite was the wrong line exactly as the PR corrects it; and the detached CLI collapsed flag and env into one wire field, making flag-over-file precedence structurally unreachable manager-side.
  • Open-brief: 30 of 69 host personas declare agent: (22 jcode, 8 claude), all registered connectors, so honoring them changes no running seat's harness and introduces no unresolved connector name.
  • And yes, as a matter of record: I read the best-effort pre-materialization read (spawnRequiredExtensions consulting the persona pin via a cwd walk that can miss the target root) as a bounded, defensible exception whose worst case was a suboptimal install hint, because the spawn body would still load the persona from the resolved target root and fail loud at the boundary it always had. That assessment was wrong, and Part 2 is why.

PART 2. My position now, and how I got there:

  • I re-derived the finding rather than accepting it. By reading: command.ts:193-196 materializes cmd.requiredExtensions(parsed) before cmd.run(parsed), and the throw is not caught between those points. By execution: driving the real spawnRequiredExtensions on a persona pinning a connector that is neither registered in-process nor installed, materializeExtension threw "no connector registered" before any spawn body ran.
  • The re-derivation surfaced a mechanism worse than "a hint can be wrong": the gate and the body read the persona from DIFFERENT roots. The gate reads it via the findCotalRoot() walk from the invocation cwd; the body reads it from the resolved target root. When those disagree (an operator spawning into another mesh's workspace, or a scratch cwd whose walk lands elsewhere), the gate demands the connector named by the WRONG persona, and materializeExtension throws on an uninstalled name, aborting a spawn that pre-fix would have launched correctly on the pinned harness. The PR's limitation paragraph ("the wrong-connector installation hint can be suboptimal, but the agent still runs on the persona's harness") is therefore not just optimistic about hints: it is incorrect about the outcome. The abort is a hard failure introduced by the fix itself, on a path the pre-fix code handled correctly, and it is silent about the disagreement (the operator sees an install hint for a connector their target persona never named).
  • What would change my mind: make the gate's persona read resolve from the same root the command body will resolve (or defer the pin contribution until after argv resolves the target), so a root disagreement cannot abort the spawn; keep or drop the best-effort character as a separate decision. Alternatively, a loud failure that names the disagreement would at least stop the silent wrong-demand shape, but the gate reading a different persona than the body is the defect to fix.
  • Unchanged from Part 1: the loader, writer, precedence, detached collapse, and reachability work is verified and good, and the docs-bundle fixpoint at the current head 14b6ce4 holds (regeneration is a byte-identical no-op; the intervening commits are punctuation-only, which I diffed). The block is specifically the pre-materialization gate.

Heads: graded at 6c938dc, re-resolved at the current head 14b6ce4 (negative control on an invented tail for both). The block applies to 14b6ce4; the intervening commits change only docs punctuation.

Model line: glm-5.3, the same family that authored this PR, so treat this verdict accordingly where independence matters: Part 1 was independent in process (no disclosure), Part 2 was disclosure-triggered but re-derived at the artifact before I changed position.

Named gaps: the live detached spawn path against a running shared manager was not exercised (forbidden here); CI was not read, so no CI result is cited.

Cotal added 9 commits August 28, 2026 08:19
Cell E must drive the real runCli dispatcher with installed-mode extension
resolution on, an empty scratch manifest, and no pre-registered connectors, so
the refusal it grades is produced by the same path the published binary takes.
The recorders are unregistered for the dispatch and restored right after; the
exit the refusal path takes is intercepted so the suite survives to grade it.
The runCli boot reconciles seeded connectors before dispatch, and in a fresh
XDG root a first run stages and installs the default connectors. That erases
the not-installed world cell E grades and made the cell flaky. Borrow the
same skip the published binary uses for its internal children.
…emption

The position freeze that justified the UNGATED entry was repealed on main
(tail appends are the established pattern and move zero shards), so the
exemption asserted a constraint that no longer exists. Appended at the file
tail (round-robin shard walk: zero existing suites re-sharded) and removed
the obsolete entry.
@davidfarah2003
davidfarah2003 merged commit 9d64c39 into main Aug 28, 2026
13 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

persona frontmatter agent: is silently ignored - COTAL_DEFAULT_AGENT wins over the file's explicit choice

1 participant