Skip to content

ceo agent has no cross-loop working memory; multi-loop ceo restarts exploration from zero each time #6

Open

Description

@jerryleooo

Summary

Across 3 different takeover projects (TypeScript SEO pipeline, Hugo + Python content site, Django + React SaaS), each running wanman run --loops 3, all 9 loops were classified idle with reason "no state changes detected". ceo agent was actively running tools (Bash/Read/Skill) in every loop but never reached wanman task create / wanman initiative create. Hypothesized root cause: each loop spawns a fresh ceo subprocess with no memory of prior loops' exploration, so multi-loop budget cannot productively accumulate.

Reproduction

  1. Customize three takeover overlays (different agents.json rosters, project-specific skills + AGENT.md operating manuals)
  2. For each project: cd <project>/.wanman && wanman run "<goal>" --loops 3
  3. Inspect deliverables/loop-events.ndjson and wanman.db

Observed evidence

Project A (TS stack)        spawns: Counter({'ceo': 8})   classifications: 8× idle
Project B (Hugo+Py stack)   spawns: Counter({'ceo': 3})   classifications: 3× idle
Project C (Django+React)    spawns: Counter({'ceo': 3})   classifications: 3× idle

In each project:

  • wanman.db ended at 0 bytes (no rows in initiatives/tasks/capsules tables — sqlite3 wanman.db "SELECT * FROM initiatives" returns Error: no such table: initiatives)
  • Each ceo agent's output/ directory was never created (ceo wrote nothing)
  • Each loop ndjson event: {"type":"loop.classified", "classification":"idle", "reasons":["no state changes detected"]}

Typical per-loop log pattern observed:

07:55:08 agent-process  spawning agent (ceo)
07:55:13 agent-process  tool_call (ceo, tool:Skill)
07:55:18 agent-process  tool_call (ceo, tool:Read)
07:55:34 agent-process  tool_call (ceo, tool:Bash)
... [several minutes of Read/Bash/Skill calls reading docs]
loop ends → next loop spawns FRESH ceo subprocess → repeats reading from zero

Why this looks structural, not just a prompt issue

Transparent caveat: my AGENT.md gave ceo a heavy startup sequence (read 5-8 canonical files + 2-3 skill files before acting). The first loop exhausting on exploration is plausibly a prompt issue.

But the structural concern: even with a shorter prompt, the nth loop still spawns a fresh ceo subprocess that has no memory of what loop n-1 read or concluded. So --loops N cannot help an exploration-then-act problem because each loop is independent. The only path to a productive run is for a single loop's session to fit the entire (read + plan + write) cycle in its time budget.

Verified the multi-loop assumption from ndjson: every loop emits an agent.spawned event for ceo, and every loop is independently classified.

Impact

  • "Take a moment to deeply understand the project" is a natural ceo behavior on a fresh takeover, so this hits new users immediately
  • 0%-productive runs leave wanman.db schema un-initialized → subsequent wanman task list / wanman initiative list error with "no such table"
  • The supervisor sends [local] Mission control: sent backlog-drained steer to ceo after the first idle loop, but the steer cannot help if the next loop's new ceo has to start from zero anyway

Possible directions

  1. Persist ceo working memory across loops — write a per-agent scratchpad / session summary to wanman.db at end of each loop, prepend to next loop's prompt context
  2. Single long-running ceo process that treats loop boundaries as logical checkpoints rather than process boundaries
  3. Pre-resolved project digest — at supervisor start, run a one-time "project digest" agent whose output is cached and prepended to every subsequent ceo loop (avoid every ceo re-reading the same source files)
  4. Documentation — if "fast-acting first loop" is the design intent, document the AGENT.md startup-sequence constraint explicitly so users don't author read-heavy prompts

Acceptance

  • Across multi-loop runs on a fresh project, the second or third loop's ceo benefits from the first loop's exploration (no zero-restart)
  • After 3 loops on a previously-fresh project, at least 1-3 initiatives or tasks exist (or supervisor explicitly explains why not in deliverables)
  • wanman.db schema is initialized even on idle loops (avoid "no such table" errors when users inspect state mid-run)

Environment

  • wanman: built from chekusu/wanman main (last pushed_at 2026-04-25)
  • runtime: claude (default)
  • Node 20+, pnpm 9+
  • Single-host, local supervisor mode (no db9 brain attached)

Activity

  1. jerryleooo commented on Apr 28, 2026

    @jerryleooo
    Author

    Self-correction after reading the code

    I owe a refinement to the original framing. After reading packages/runtime/src/preamble.ts and agent-process.ts:252-257, I see wanman does propagate state across 24/7 respawns via the preamble system — I overstated the problem.

    What wanman actually does today

    On every 24/7 respawn, generatePreamble() injects a markdown block into the agent's prompt:

    ## System Context (Loop #N)
    Run ID: ... | Uptime: ...
    
    > ⚠ You were interrupted by a steer message. Reason: ...   (if any)
    
    ### Current Tasks
    - ⏳ [id] Title (assignee)
    - ...
    
    ### Recent Messages
    - agent-name: payload preview...
    

    So tasks and messages do survive respawn boundaries. My original claim "no cross-loop working memory" was too strong.

    The precise gap

    The preamble only surfaces already-externalized state (rows in tasks, rows in messages). It does not capture an agent's in-flight exploration:

    • files read but not yet acted on
    • working hypotheses / draft reasoning
    • "I'm partway through reading docs/X.md, next loop pick up at line 200"

    In our reproduction, ceo's loop 1 spent 4-5 minutes on Read/Bash/Skill exploration but never reached wanman task create or wanman send. So the preamble for loop 2 contained essentially nothing:

    ## System Context (Loop #2)
    Run ID: ... | Uptime: 4m...
    

    Empty. Loop 2's ceo restarted exploration from zero. Loop 3 same. Compounded across 11 loops in the rebuilt-with-PR-#2 run, the result was still 11 loops, 0 productive (0%).

    Refined problem statement

    wanman has a cross-respawn state mechanism (preamble), but it only carries externalized state (tasks/messages). The "exploration phase" — between agent spawn and the first DB write — is invisible to subsequent respawns and its cost is wasted. For agents whose first useful action is gated on reading several context files (e.g., a CEO agent told to read canonical docs before decomposing tasks), this can cause indefinite zero-progress loops.

    Refined acceptance for the fix

    • Agents can persist a "scratchpad" / "session note" between respawns (e.g., a wanman scratchpad set CLI or an automatic transcript-summary write at loop end)
    • generatePreamble() includes scratchpad content alongside tasks/messages
    • Even on a 0-task respawn, the next loop's preamble carries forward "where I left off in the exploration"

    This is meaningfully smaller in scope than my original "needs cross-loop working memory" framing — the existing preamble plumbing covers most of the wiring. The missing piece is just one more state column (per-agent scratchpad) and a CLI/auto-mechanism to populate it.

    Side note on PR #2 (idle_cached + Claude session resume)

    For completeness: the merged idle_cached + claude --resume path solves a related-but-different problem (preserving Claude's conversation history across on-demand triggers). It does not apply to the 24/7 respawn path described above — agent-process.ts:430 only reads lastSessionId when lifecycle === 'idle_cached'. So PR #2 is great for stateful on-demand roles ("support" agent that remembers the customer) but doesn't change ceo-style continuous loops.

    Confirmed by re-running the same 3 projects with a freshly rebuilt wanman after PR #2 merge: all loops still classified idle, no productivity change. (Expected — different code path.)

    Apologies for the imprecise original wording.

  2. guo-yu commented on May 16, 2026

    @guo-yu
    Contributor

    I re-checked this against current main, and the refined version of the issue is confirmed by the code path.

    What I found:

    • packages/runtime/src/preamble.ts only accepts tasks, recentMessages, loop metadata, and optional steerReason. There is no scratchpad/session-note input today.
    • The 24/7 path in packages/runtime/src/agent-process.ts respawns a fresh CLI subprocess every loop. It injects the generated preamble before the goal prompt, but does not pass a resumed Claude session id in this path.
    • lastSessionId / resumeSessionId is only used for idle_cached trigger runs, not for the 24/7 respawn loop. So your side note about PR feat(runtime): add idle_cached lifecycle + Claude session resume #2 not helping this specific case is accurate.
    • I did not find a scratchpad concept in the runtime/core types or CLI commands.

    So the precise gap is not “no state at all across loops”; the existing preamble carries externalized state. The gap is that exploration state before the first DB/message write is invisible to later loops.

    A good implementation shape would be:

    1. Add a per-agent scratchpad/session-note store, probably keyed by runId + agentName.
    2. Add either an explicit CLI (wanman scratchpad set/append) or an automatic end-of-run summary hook fed by the adapter event log.
    3. Extend PreambleInput / generatePreamble() to include a concise ### Scratchpad section.
    4. Add a regression test where loop 1 writes only scratchpad state, loop 2 receives that scratchpad even when there are still zero tasks/messages.

    I would keep this open as a valid runtime issue. It is smaller than the original framing but still important for read-heavy takeover prompts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions