You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Exercising the persistence API to see whether a NOOA agent could drive a long-running workflow that pauses for a human β an operator supplying a value partway through β and then finishes later, possibly in a different process.
Two things came out of that, one small and one structural. The small one I've already opened a PR for (#139, the StorageManager docstring documents agent.save() / MyAgent.load(...), neither of which exists). This issue is about the structural one, which I'd rather ask about than assume.
The gap
AGENTS.md:32 is explicit that orchestrators are pure Python:
Orchestrators are pure Python. Workflow sequence methods have real bodies (no ...), calling generation methods for each step. If a class has no ... methods it doesn't need to subclass Agent at all.
That works well, but a Python method's progress lives on the call stack, and nothing captures the stack:
AgentSnapshot.from_agent walks agent.__dict__ (src/nooa/storage/snapshot.py:100) β it captures attributes, not the frame, the local variables, or the program counter.
CodeActStrategy runs a live in-process loop (while not session.is_exhausted(), src/nooa/strategies/codeact.py:827) with no suspension point.
Grepping src/nooa/ for suspend / pause / resume_from finds nothing.
So an orchestrator that is three steps in and waiting on a human cannot be resumed after the process exits. InteractiveAgent (src/nooa/interactive.py:310) does give durable progress between turns, but that's a chat-shaped loop β it doesn't help a run() method that is halfway down its own body.
The workaround, and why I'm not sure it's the intended one
Keep the orchestrator's progress in a snapshotted field rather than in locals, so re-entering the method after restore_snapshot() skips whatever is already done:
classPipelineAgent(Agent, llm=llm):
steps_done: dict[str, str] # snapshotted β this is the resumable partinputs: dict[str, str]
asyncdefrun(self, payload: str) ->str:
if"summary"notinself.steps_done:
self.steps_done["summary"] =awaitself.summarise(payload)
if"threshold"notinself.inputs:
raiseSuspendForInput("what risk threshold should apply?")
...
This works today with no library changes. I've put a runnable version up as #140 β it runs a step, suspends, snapshots, re-launches itself as a genuinely separate process, restores, and finishes. The orchestrator prints which steps it actually executes, so the child visibly runs only the remaining one; the earlier step comes back from the snapshot rather than being repeated.
But every user who wants this has to independently invent the ledger, remember that locals aren't durable, and get the re-entrancy right. That felt like something worth asking about rather than quietly working around.
The question
Is the field-ledger pattern the intended answer, or is durable orchestration something the framework should carry?
"In scope, we'd take a PR" β happy to work it up, in whatever shape you prefer (an optional workspace package alongside nooa-memory / nooa-bench, or something smaller). I'd want a design agreed here first rather than arriving with a large PR.
What I was doing
Exercising the persistence API to see whether a NOOA agent could drive a long-running workflow that pauses for a human β an operator supplying a value partway through β and then finishes later, possibly in a different process.
Two things came out of that, one small and one structural. The small one I've already opened a PR for (#139, the
StorageManagerdocstring documentsagent.save()/MyAgent.load(...), neither of which exists). This issue is about the structural one, which I'd rather ask about than assume.The gap
AGENTS.md:32is explicit that orchestrators are pure Python:That works well, but a Python method's progress lives on the call stack, and nothing captures the stack:
AgentSnapshot.from_agentwalksagent.__dict__(src/nooa/storage/snapshot.py:100) β it captures attributes, not the frame, the local variables, or the program counter.CodeActStrategyruns a live in-process loop (while not session.is_exhausted(),src/nooa/strategies/codeact.py:827) with no suspension point.src/nooa/forsuspend/pause/resume_fromfinds nothing.So an orchestrator that is three steps in and waiting on a human cannot be resumed after the process exits.
InteractiveAgent(src/nooa/interactive.py:310) does give durable progress between turns, but that's a chat-shaped loop β it doesn't help arun()method that is halfway down its own body.The workaround, and why I'm not sure it's the intended one
Keep the orchestrator's progress in a snapshotted field rather than in locals, so re-entering the method after
restore_snapshot()skips whatever is already done:This works today with no library changes. I've put a runnable version up as #140 β it runs a step, suspends, snapshots, re-launches itself as a genuinely separate process, restores, and finishes. The orchestrator prints which steps it actually executes, so the child visibly runs only the remaining one; the earlier step comes back from the snapshot rather than being repeated.
But every user who wants this has to independently invent the ledger, remember that locals aren't durable, and get the re-entrancy right. That felt like something worth asking about rather than quietly working around.
The question
Is the field-ledger pattern the intended answer, or is durable orchestration something the framework should carry?
Concretely, any of these is a useful reply:
feat: add shared interactive session foundationadds durable sessions with persistence and resumption. It touchessqlite.pyrather than the orchestrator layer, so my read is that they're complementary, but you'd know better. If feat: add shared interactive session foundationΒ #82 is heading somewhere that makes examples: durable human-in-the-loop suspend and resume π€π€π€Β #140 redundant, I'd rather hear it now than have you carry a stale example.nooa-memory/nooa-bench, or something smaller). I'd want a design agreed here first rather than arriving with a large PR.No urgency on my end. Mostly I wanted the gap written down somewhere, since I couldn't find it discussed in any existing issue or PR.