Skip to content

fix(spawn): persist the resolved default agent on the session#221

Open
codebanditssss wants to merge 1 commit into
mainfrom
fix/persist-resolved-harness
Open

fix(spawn): persist the resolved default agent on the session#221
codebanditssss wants to merge 1 commit into
mainfrom
fix/persist-resolved-harness

Conversation

@codebanditssss

Copy link
Copy Markdown
Collaborator

Closes #220.

Problem

A worker/orchestrator spawned without an explicit --harness runs the daemon's default agent (claude-code) but stored an empty harness. Caught live: a session running Claude Code showed Agent: codex in the inspector, and GET /api/v1/sessions returned no harness.

Root cause: effectiveHarness("", …) returns "", seedRecord persists it, and the empty→default resolution lives only inside agentRegistry.Agent (lifecycle_wiring.go) — never written back. The empty harness is then omitted from the DTO, so the frontend's toAgentProvider(undefined) falls back to "codex".

Change

Inject the daemon's default agent (AO_AGENT / config.DefaultAgent) into the session manager and resolve an unspecified harness to it before the seed row is written — so the stored/returned harness matches the agent that actually launches. Backend-only: once harness is present, the frontend already maps claude-code → its label correctly.

Test

  • TestSpawn_PersistsResolvedDefaultHarness: an unspecified harness persists the injected default; an explicit harness still wins.
  • go build ./..., full go test ./..., gofmt, golangci-lint all clean.
  • End-to-end (isolated daemon): ao spawn with no --harness now returns harness = "claude-code" (was empty).

Notes

  • Forward-looking: sessions already spawned with an empty harness keep it until re-spawned (no migration).
  • Leaves the frontend's toAgentProvider default as-is; it's now rarely reached since the API always carries the real harness. A follow-up could make that fallback "unknown" rather than "codex".

A spawn with no explicit harness ran the daemon default (claude-code) but
stored an empty harness: effectiveHarness returned "", seedRecord persisted it,
and the empty->default resolution lived only inside agentRegistry.Agent. The API
then omitted harness and the UI defaulted to "codex" — mislabelling a Claude
Code session.

Inject the daemon's default agent (AO_AGENT / config.DefaultAgent) into the
session manager and resolve an unspecified harness to it before the seed row is
written, so the stored/returned harness matches the agent that actually runs.

Closes #220
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where spawning a session without an explicit --harness stored an empty harness in the database, causing the API to omit the field and the frontend to fall back to showing \"codex\" regardless of the actual running agent. The fix resolves the daemon's default agent once in startSession, passes it as DefaultHarness to the session manager, and fills any still-empty harness in Spawn before the seed row is written.

  • lifecycle_wiring.go: Resolves cfg.Agentconfig.DefaultAgent once and threads the concrete value into both buildAgentResolver and sessionmanager.Deps.DefaultHarness, so both the launcher and the record writer share the same effective default.
  • manager.go: After effectiveHarness (which applies explicit and per-project role-override precedence), a single guard fills an empty cfg.Harness with m.defaultHarness before seedRecord is called — preserving the correct explicit > role-override > daemon-default precedence.
  • manager_test.go: TestSpawn_PersistsResolvedDefaultHarness locks the fix with two cases: unspecified harness resolves to the injected default, and an explicit harness still wins.

Confidence Score: 5/5

Safe to merge; the change is small, targeted, and adds no new failure modes — it only fills a field before a write that was previously left empty.

The fix touches exactly two code sites: one guard in Spawn and one dep field in wiring. Precedence is preserved (explicit harness and per-project role overrides are resolved before the daemon-default fill), existing sessions with an empty harness continue to work via the agent registry's own fallback, and the new test covers both the default-fill and the explicit-override path. No data migration is needed and the PR notes the forward-compatibility concern clearly.

No files require special attention.

Important Files Changed

Filename Overview
backend/internal/daemon/lifecycle_wiring.go Resolves the default agent once in startSession, sharing the same value with buildAgentResolver and the session manager's DefaultHarness dep; eliminates the divergence between what runs and what is stored.
backend/internal/session_manager/manager.go Adds defaultHarness field to Manager; inserts a single-line guard in Spawn after effectiveHarness to fill an empty harness with the daemon default before the seed row is written — correct precedence (explicit > role-override > daemon default).
backend/internal/session_manager/manager_test.go Adds TestSpawn_PersistsResolvedDefaultHarness covering both the default-fills case and the explicit-harness-wins case; all existing tests remain unaffected because newManager() leaves DefaultHarness empty.

Sequence Diagram

sequenceDiagram
    participant caller as CLI / API caller
    participant wiring as lifecycle_wiring.go
    participant mgr as Manager.Spawn
    participant store as Store.CreateSession
    participant resolver as agentRegistry.Agent

    Note over wiring: cfg.Agent == empty → defaultAgent = config.DefaultAgent
    wiring->>wiring: resolve defaultAgent once
    wiring->>mgr: "New(Deps{DefaultHarness: defaultAgent})"
    wiring->>resolver: buildAgentResolver(defaultAgent)

    caller->>mgr: "Spawn(cfg{Harness: empty})"
    mgr->>mgr: effectiveHarness(empty, kind, projectCfg) → empty
    mgr->>mgr: "cfg.Harness empty → cfg.Harness = defaultHarness (NEW)"
    mgr->>store: "CreateSession(seedRecord{Harness: claude-code})"
    store-->>mgr: "SessionRecord{Harness: claude-code}"
    mgr->>resolver: Agent(claude-code)
    resolver-->>mgr: claudeCodeAdapter
    mgr->>mgr: runtime.Create(...)
    mgr-->>caller: "SessionRecord{Harness: claude-code}"
    Note over caller: API/UI now sees harness=claude-code, not empty
Loading

Reviews (1): Last reviewed commit: "fix(spawn): persist the resolved default..." | Re-trigger Greptile

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.

Session spawned without explicit --harness stores empty agent; UI mislabels it as codex

1 participant