i365dev / i365dev/free4chat

enhance(task): long-lived, resumable and bounded-concurrent Agent execution

Closed
#421 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
1.2k
Forks
166
Avg merge
1h 5m
Merged PRs (30d)
246

Description

Why this exists

Dogfood around #409 / #420 exposed a Runtime execution-model limit that is now a real product problem rather than an abstract optimization.

Free4Chat increasingly treats a Task as a retained Agent work session. Real coding/Agent workflows may run for tens of minutes or several hours, while the Human may leave the Room and return later.

Today one resident Agent is effectively a single global execution lane:

Resident Agent
├─ Task A / session A
├─ Task B / session B
└─ Task C / session C

ResidentRuntime
  turnRunning = one global turn

ACPAdapter
  promptActive = one global prompt

=> A blocks B and C

This creates three related product problems:

  1. Long-lived work

    • ACPAdapter currently defaults TurnTimeoutMs to 120000 ms (2 minutes).
    • Production can override it with FREE4CHAT_ACP_TURN_TIMEOUT_MS, but a fixed short timeout is not an acceptable product model for real Agent tasks.
    • Real sessions may legitimately work for 20 minutes, 1 hour, or several hours.
    • A timeout should recover a truly stuck Harness, not define the maximum useful Task duration.
  2. Resumable supervision

    • The Human should be able to leave the Room/browser while the local Agent continues working.
    • Later the Human may re-enter, reopen the Task, inspect its current/coarse state, continue the same retained Harness session, interrupt it if still running, or send follow-up work.
    • Continuous high-frequency live monitoring is NOT required for correctness.
  3. Head-of-line blocking across independent Tasks

    • ResidentRuntime.turnRunning serializes all scopes for the whole resident.
    • ACPAdapter.promptActive serializes all prompts for the whole ACP connection.
    • Therefore Task B cannot make progress while unrelated Task A is running, even when A and B are different retained Harness sessions.
    • Session Picker / existing-session continuation makes this much more visible because users will naturally start multiple independent Tasks against different sessions.

Refs #51, #409, #420.


Product target

A Free4Chat Agent participant should support:

long-lived local Agent work
+ Human may disconnect/reconnect
+ retained Task/session can be resumed
+ independent Tasks can make bounded concurrent progress

Example:

Task A / Session A
  running for 1h+
  Human leaves Room

Task B / Session B
  can still start if execution capacity exists

later:
Human rejoins Room
→ opens Task A
→ sees truthful coarse state
→ continues/interacts with same retained session

Semantic rule:

same Task / same Harness session
→ always serialized

different Task / different Harness session
→ may run concurrently when that Harness/provider is VERIFIED safe

Do NOT introduce unlimited concurrency.

A reasonable initial target may be a small bounded number of execution lanes such as 2, but the exact limit must be earned by measurement.


UX: resumable first, lightweight realtime second

The product should NOT require the Human to keep the Room open.

Desired lifecycle:

Human starts Task
→ local Agent works
→ Human closes browser / leaves Room
→ Agent may continue locally
→ Human later rejoins
→ Task can be reopened/resumed
→ same Harness session remains the conversation/work boundary

The Task UI on re-entry should recover a truthful bounded state such as:

Running
Queued
Waiting for approval
Completed
Failed
Interrupted
Session lost

plus enough retained output/artifacts to understand what happened.

Lightweight realtime is useful

Do not interpret "resumable" as "no live signal".

A connected Human should still be able to see that an Agent is alive and working, but the communication should be tiny and coarse.

Prefer small state deltas such as:

Working
Thinking
Using tools
Waiting for approval
lastActivityAt
current Task id / turn sequence

Possible behavior:

state changed
→ emit one tiny update

state unchanged for a long time
→ optional low-frequency liveness pulse

Human disconnected
→ no browser-driven polling required

Human reconnects
→ one bounded refresh/reconciliation

Do NOT stream every model token, every tool progress tick, full chain-of-thought, or continuous high-frequency telemetry.

The live signal must remain cheap enough that a 1–3 hour Task is economically boring.

This protects Durable Object awake-time/request cost, WebSocket/DataChannel traffic quotas, mobile battery/network, and local Runtime overhead.

Realtime transport is presentation, not execution ownership.


Current implementation evidence

Runtime-level global serialization

ResidentRuntime currently owns one global turnRunning bool, and drainTurns() will not start another canonical turn while it is set.

Logical Task scopes are isolated for conversation state, but not for execution capacity.

ACPAdapter-level global serialization

ACPAdapter.RunTurnFor(...) currently rejects another prompt while promptActive == true.

Prompt-local state is also global:

turnChunks
turnContext / turnCancel
turnSessionID
pendingPermissions

So simply making Runtime per-scope concurrent is not sufficient; the Harness boundary must isolate active turns correctly.

Current turn timeout

Default:

defaultTurnTimeoutMs = 120000

with optional:

FREE4CHAT_ACP_TURN_TIMEOUT_MS

This is useful as a safety/recovery control, but it is too short to define the product's maximum Task duration.

Pi evidence

Current pi-acp can retain multiple sessions and session/load creates a Pi RPC process for the selected session.

That suggests cross-session parallelism may be technically possible, but it must be verified with a real concurrency probe before Free4Chat enables it.

Do not infer concurrency support merely from ACP session support.

Claude/Codex/OpenCode/Hermes likewise require real verification.


Separate Task duration from stuck-turn recovery

The current fixed wall-clock timeout conflates:

legitimate long-running task duration

with:

Harness appears stuck / no useful progress

These must be separated.

Investigate a model such as:

Task lifetime:
  may be hours

turn recovery:
  bounded watchdog / provider-specific safety policy

progress:
  coarse trustworthy signals when available

Human:
  can interrupt at any time

A possible direction is an idle/progress watchdog rather than a short total-duration timeout.

Do not implement an indefinite unbounded hang with no recovery boundary.

For Harnesses with weak/no progress signals, define a conservative provider-specific policy.

A hard safety ceiling may still exist, but it must be much larger than normal useful Task duration and must not be confused with normal Task UX.


Resumption boundary

Keep three cases distinct.

A. Human/browser disconnect

This is the primary product requirement.

Browser gone / Room tab closed / Human temporarily leaves must NOT stop the local Task.

On rejoin:

same Room / Task identity
→ recover coarse execution projection
→ recover latest retained outputs/artifacts
→ reconnect controls
→ continue same Harness session

No requirement to replay all live activity.

B. Resident transport reconnect

A transient Room ↔ Runtime socket reconnect should preserve Task/session ownership whenever the local Harness work is still alive.

The transport is not the execution owner.

C. local free4chat-agent / Harness process restart

This is different.

Do not claim durable execution across local process death unless the Harness itself supports it.

If execution died but the underlying Harness session remains recoverable:

Task may become Session lost / interrupted
→ Human can explicitly resume/restart from retained session

Represent this truthfully rather than fabricating continuous execution.


Multiple Tasks UX

When capacity is available:

Task A → Running
Task B → Running

When bounded concurrency is full:

Task A → Running
Task B → Running
Task C → Queued

The Human must be able to tell that Task C is waiting for an execution lane rather than appearing stuck.

A reconnecting Human should see the same truthful state without requiring a continuously connected browser.

Same-session safety

Two turns targeting the same retained Harness session must never run concurrently.

Task A follow-up 1
Task A follow-up 2
→ serialized

Task A and Task B both bound to same native session
→ serialized or rejected according to the final ownership model

No concurrent writes into one conversation.

Remote control

Interrupt must remain scoped to the exact active Task turn/execution lane.

Interrupting Task A must never cancel Task B.

Permission requests must likewise remain lane/session scoped.


Provider policy

Concurrency must be an explicit VERIFIED product capability, similar to Task Session Continuation.

Conceptually:

TaskExecutionPolicy
  longRunning: verified
  concurrency:
    mode: serial | cross-session
    maxConcurrent: N

Exact type/shape is open.

Important rule:

ACP supports multiple sessions
!=
provider safely supports concurrent prompts

Current default for every Harness should remain serial until a real probe proves otherwise.


Investigation: execution model

Compare two narrow designs before implementation.

Option A — multiplex one ACPAdapter

Refactor active prompt state from global to per-session/per-scope:

activeTurns[scope/session]
chunks[scope/session]
cancel[scope/session]
permissions[scope/session]

Pros:

  • one ACP process/connection;
  • potentially lower process overhead.

Risks:

  • deeper adapter complexity;
  • stream/update routing must be exact;
  • cancel/permissions/failure isolation become more subtle.
Option B — bounded adapter/process lanes

One resident owns a small pool:

ResidentRuntime
├─ execution lane A → ACPAdapter A
└─ execution lane B → ACPAdapter B

Each lane is isolated.

Pros:

  • prompt/chunk/cancel/permission/failure isolation is simpler;
  • current ACPAdapter can remain mostly single-prompt;
  • one stuck Harness process need not corrupt another lane.

Costs:

  • more local processes/RAM;
  • session discovery/control and execution adapter ownership need a clean seam;
  • must cap lanes aggressively.

Do not choose by aesthetics. Measure real provider behavior and local resource cost.


Required real probes

Run real probes before enabling concurrency for any built-in Harness.

Pi

At minimum:

session A → long prompt/tool work
session B → independent prompt

verify:
- both actually make progress concurrently
- chunks route to correct session
- cancel A does not cancel B
- permission A does not resolve B
- one Pi child crash does not corrupt unrelated session
- session histories remain correct
- CPU/RAM/process cost
Claude / Codex / OpenCode / Hermes

Repeat only where their current bridge/runtime exposes enough session support.

Classify each:

VERIFIED CROSS-SESSION
VERIFIED SERIAL
UNVERIFIED
UNSUPPORTED

Do not enable based on source reading alone.


Required Runtime properties

Any final design must preserve:

  • Per-session serialization — at most one active turn for one Harness session.
  • Bounded cross-session concurrency — small explicit max; no unbounded lane spawning.
  • Fairness — a long-lived Task must not starve all other independent sessions. Keep this simple; avoid a general-purpose scheduler.
  • Exact interrupt ownership — (Task scope, turn sequence, execution lane) resolves to one exact active Harness turn.
  • Failure isolation — lane A failure must not silently destroy healthy lane B when the provider/process model allows isolation.
  • Session loss remains fail-closed — no fresh replacement of an adopted/continued session.
  • Low-cost observability — execution state must not require a high-frequency stream.
  • Restart truthfulness — define what happens if local free4chat-agent restarts; do not invent durable execution when the Harness cannot provide it.

Cost / capacity requirements

This feature runs primarily on the Human's machine, but resource use still matters.

Measure at least:

idle resident RAM
1 active Task RAM/CPU/process count
2 concurrent Tasks
4 concurrent Tasks (probe only, not necessarily product)

network bytes/minute for coarse live activity
Room/DO request rate while Human connected
Room/DO request rate while Human disconnected

Target:

Human disconnected + Agent working for hours
→ near-zero Room-side traffic except genuinely useful state transitions

Human connected
→ tiny, bounded activity traffic

No polling loop that keeps the Room DO awake.


Non-goals

Do NOT turn this into:

  • a generic distributed scheduler;
  • cloud-hosted Agent execution;
  • durable job queue infrastructure;
  • a central Agent session database;
  • unlimited parallel Tasks;
  • full token/tool-log streaming;
  • permanent workspace semantics;
  • background execution guarantees across local machine shutdown.

Suggested delivery strategy

Do not start with a repo-wide concurrency rewrite.

  1. Measure/probe Pi + other Harness behavior.
  2. Define the smallest provider execution policy.
  3. Separate long-task lifetime from stuck-turn watchdog.
  4. Establish resumable/coarse Task state across Human disconnect/reconnect.
  5. Add bounded cross-session execution lanes only where verified.
  6. Dogfood with a real 30–60+ minute Task and at least two independent sessions.

Success criterion:

A Human can start a long Agent Task, leave Free4Chat, later return and continue supervising the same retained session, while an unrelated Task can still make progress when provider capacity allows — without high-frequency Room/DataChannel traffic or unbounded local resource use.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ResidentRuntime.turnRunning and drainTurns(), then inspect ACPAdapter.RunTurnFor(), promptActive, and defaultTurnTimeoutMs to map the current global execution ownership. Run the required real provider probes, beginning with Pi sessions A and B, and record routing, cancellation, permission, failure-isolation, and resource behavior. Done means the selected design preserves same-session serialization, bounded verified concurrency, truthful queued/resumable state, and scoped interrupts.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
ai, backend, distributed-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.