Skip to content

[Feature]: centralize hosted conversation runtime overrides#1194

Merged
chumyin merged 12 commits intoeastreams:devfrom
chumyin:feat/runtime-host-foundation-20260410
Apr 17, 2026
Merged

[Feature]: centralize hosted conversation runtime overrides#1194
chumyin merged 12 commits intoeastreams:devfrom
chumyin:feat/runtime-host-foundation-20260410

Conversation

@chumyin
Copy link
Copy Markdown
Collaborator

@chumyin chumyin commented Apr 10, 2026

Summary

  • Problem: loong tasks owned a bespoke DetachedTasksRuntime that reimplemented the full ConversationRuntime trait only to swap the background-task spawner.
  • Why it matters: this duplicated runtime bootstrap behavior, increased drift risk, and made it easier for runtime-owned surfaces to fork their own private wrappers instead of sharing one truth-preserving seam.
  • What changed: introduced a reusable HostedConversationRuntime<R> wrapper in the conversation runtime layer, delegated the full runtime trait through it, exposed explicit async/background spawner overrides, and switched tasks CLI to the shared wrapper.
  • What did not change (scope boundary): no task semantics change, no runtime caching, no persistent AgentRuntimeHost yet.

Linked Issues

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Security hardening
  • CI / workflow / release

Touched Areas

  • Kernel / policy / approvals
  • Contracts / protocol / spec
  • Daemon / CLI / install
  • Providers / routing
  • Tools
  • Browser automation
  • Channels / integrations
  • ACP / conversation / session runtime
  • Memory / context assembly
  • Config / migration / onboarding
  • Docs / contributor workflow
  • CI / release / workflows

Risk Track

  • Track A (routine / low-risk)
  • Track B (higher-risk / policy-impacting)

Validation

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --locked
  • cargo test --workspace --all-features --locked
  • Relevant architecture / dep-graph / docs checks for touched areas
  • Additional scenario, benchmark, or manual checks when behavior changed
  • If this changes config/env fallback, limits, or defaults: include before/after behavior and regression coverage for explicit path, fallback path, and boundary values
  • If tests mutate process-global env: document how state is restored or serialized

Commands and evidence:

cargo fmt --all -- --check
CARGO_TARGET_DIR=<redacted-target-dir> cargo clippy -p loongclaw-app -p loongclaw --all-targets --all-features -- -D warnings
CARGO_TARGET_DIR=<redacted-target-dir> cargo test -p loongclaw-app hosted_runtime --lib -- --test-threads=1
CARGO_TARGET_DIR=<redacted-target-dir> cargo test -p loongclaw --test integration execute_tasks_command_create_queues_background_task_and_surfaces_follow_up_recipes -- --nocapture
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --locked -- --test-threads=1
cargo test --workspace --all-features --locked -- --test-threads=1
./scripts/check_architecture_boundaries.sh
./scripts/check_dep_graph.sh

User-visible / Operator-visible Changes

  • No user-facing command behavior change is intended; this is a runtime-foundation refactor that keeps task create behavior stable while reducing wrapper drift.

Failure Recovery

  • Fast rollback or disable path: revert this PR.
  • Observable failure symptoms reviewers should watch for: loong tasks create no longer queuing detached background work, or background-task spawner overrides silently not taking effect.

Reviewer Focus

  • crates/app/src/conversation/runtime.rs: hosted wrapper delegation and override precedence.
  • crates/daemon/src/tasks_cli.rs: replacement of the bespoke runtime wrapper with the shared host wrapper.
  • crates/app/src/conversation/runtime.rs tests: regression coverage for override semantics.

Summary by CodeRabbit

  • New Features

    • Tasks now include an owner_kind field surfaced in JSON and CLI output.
    • Live chat now shows first-token latency (ttft) in status and previews.
  • Refactor

    • Conversation runtime loading and spawner configuration made pluggable so task/background spawners can be overridden without altering core behavior.
    • Introduced a hosted runtime wrapper to host optional spawner overrides.
  • Tests

    • Expanded tests for spawner overrides, owner_kind propagation, and latency rendering.
  • Performance

    • HTTP client construction now reuses cached clients to reduce allocations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a reusable, feature-gated HostedConversationRuntime wrapper and loader helpers (load_default_conversation_runtime, load_hosted_default_conversation_runtime); threads a new ConstrainedSubagentOwnerKind and owner_kind through constrained-subagent execution, delegate policies, enqueue paths, task payloads, streaming latency, and updates tests and daemon CLI to use the hosted wrapper.

Changes

Cohort / File(s) Summary
Public re-exports
crates/app/src/conversation/mod.rs
Expose HostedConversationRuntime, load_default_conversation_runtime, load_hosted_default_conversation_runtime, and ConstrainedSubagentOwnerKind in the conversation public API.
Hosted runtime implementation & loader
crates/app/src/conversation/runtime.rs
Add BoxedDefaultConversationRuntime, load_default_conversation_runtime, load_hosted_default_conversation_runtime, and #[cfg(feature="memory-sqlite")] HostedConversationRuntime<R> with builders and a ConversationRuntime impl that delegates to inner runtime except for optional async-delegate/background-task spawner overrides; include tests for override precedence.
Constrained subagent model & tests
crates/app/src/conversation/subagent.rs, crates/app/src/conversation/tests.rs, crates/app/src/conversation/workspace_isolation.rs, crates/app/src/tools/runtime_config.rs
Add pub enum ConstrainedSubagentOwnerKind + as_str(); add owner_kind: Option<ConstrainedSubagentOwnerKind> to ConstrainedSubagentExecution; update fixtures and assertions to set/verify owner_kind.
Delegate policy & enqueue flow
crates/app/src/operator/delegate_runtime.rs, crates/app/src/conversation/turn_coordinator.rs, crates/app/src/conversation/delegate_support.rs
Add owner_kind: Option<ConstrainedSubagentOwnerKind> to DelegateChildExecutionPolicy; thread owner_kind into async and background-task enqueue paths; replace direct runtime construction with load_default_conversation_runtime where applicable.
Daemon tasks CLI & integration tests
crates/daemon/src/tasks_cli.rs, crates/daemon/tests/integration/tasks_cli.rs
Remove bespoke DetachedTasksRuntime; use hosted wrapper via load_hosted_default_conversation_runtime(...).with_background_task_spawner(...); include owner_kind in task payloads and CLI rendering; update integration test assertions.
Callsite runtime loader updates
crates/app/src/conversation/turn_loop.rs, crates/app/src/conversation/turn_engine.rs, crates/app/src/channel/dispatch.rs, crates/app/src/conversation/tests.rs
Replace DefaultConversationRuntime::from_config_or_env(...) usages with load_default_conversation_runtime(...) and update imports.
Streaming latency & observer plumbing
crates/app/src/conversation/turn_observer.rs, crates/app/src/acp/backend.rs, crates/app/src/chat*.rs, crates/app/src/provider/request_executor.rs
Add elapsed_ms to StreamingTokenEvent; extend streaming callbacks to accept request_started_at and compute elapsed ms; propagate first-token latency through live chat state and rendering; update tests and serializers.
HTTP client caching
crates/app/src/provider/http_client_runtime.rs
Introduce a global mutex-protected per-timeout cache for reqwest::Client with test instrumentation and unit tests for reuse/separation.
Various callsite/test updates
multiple crates/app/* and crates/daemon/* files
Adjust imports/call sites to use new loaders, set owner_kind in test fixtures where required, and update rendering/output to include owner_kind/latency fields.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as "Tasks CLI"
  participant Hosted as "HostedConversationRuntime<R>"
  participant Inner as "DefaultConversationRuntime"
  participant Spawner as "BackgroundTaskSpawner / DetachedTasksSpawner"

  CLI->>Hosted: load_hosted_default_conversation_runtime(config)
  Hosted->>Inner: wrap boxed default runtime
  CLI->>Hosted: enqueue background task (owner_kind = BackgroundTaskHost)
  Hosted->>Spawner: use overridden background_task_spawner.spawn(...)
  Spawner-->>Hosted: spawn result
  Hosted-->>CLI: return created task (owner_kind = "background_task_host")
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #1203 — Implements streaming elapsed_ms and first-token latency plumbing; changes add elapsed_ms and propagate it through observer/runtime surfaces.
  • #1198 — Adds centralized runtime loader and hosted wrapper; this PR implements those loader/wrapper changes.

Possibly related PRs

  • #542 — Modifies turn-observer streaming plumbing; closely related to the added observer callback signature and elapsed_ms propagation.
  • #451 — Changes SSE/streaming token plumbing; overlaps on StreamingTokenEvent and serialization.
  • #922 — Touches delegate-runtime and turn-coordinator surfaces; related to owner_kind propagation and delegate handling.

Suggested labels

memory, size: M

Suggested reviewers

  • gh-xj

Poem

🐰 I tuck a runtime in my paws so neat,

I wrap the inner engine, light and fleet.
Two spawners I may swap when hosts implore,
owner-kind stamped softly, then I hop for more.
Merge me in—I'll guard the tests and greet.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the primary objective: introducing a reusable HostedConversationRuntime wrapper to centralize runtime overrides, replacing bespoke implementations like DetachedTasksRuntime.
Linked Issues check ✅ Passed The code changes fully satisfy issue #1192 requirements: HostedConversationRuntime wrapper added [runtime.rs], tasks_cli replaced DetachedTasksRuntime [tasks_cli.rs], override semantics implemented for spawners, regression tests added, and existing behavior preserved.
Out of Scope Changes check ✅ Passed Owner-kind metadata tracking changes (subagent.rs, delegate_runtime.rs, turn_coordinator.rs) and first-token latency instrumentation (backend.rs, turn_observer.rs, chat.rs) extend the runtime foundation but align with follow-up objectives described in PR comments; HTTP client caching (http_client_runtime.rs) is transport-level optimization related to host-owned resource management.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added daemon Daemon binary, CLI entrypoints, and install flow. conversation Conversation runtime, session flow, and prompt assembly. size: M Medium pull request: 201-500 changed lines. labels Apr 10, 2026
@chumyin chumyin self-assigned this Apr 10, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/app/src/conversation/runtime.rs`:
- Around line 2198-2225: Add a symmetric test that verifies
with_async_delegate_spawner() overrides the async delegate spawner without
affecting the background task spawner: create a test (e.g.,
hosted_runtime_overrides_async_delegate_spawner_without_changing_background_task_spawner)
mirroring the existing hosted_runtime_overrides_background_task_spawner... test
but call
HostedConversationRuntime::new(inner_runtime).with_async_delegate_spawner(override_async_spawner),
then resolve both hosted_runtime.async_delegate_spawner(&config) and
hosted_runtime.background_task_spawner(&config) and assert Arc::ptr_eq that the
async resolver returns the override_async_spawner while the background resolver
still returns the original inner_background_spawner; use the same types/fixtures
(SpawnerAwareRuntime, LoongClawConfig, NoopTestSpawner) so the test covers the
missing override seam.
- Around line 1479-1668: The HostedConversationRuntime impl must explicitly
delegate build_context to the inner runtime instead of using the trait default;
add a build_context(&self, config: &LoongClawConfig, session_id: &str,
include_system_prompt: bool, tool_view: &ToolView, binding:
ConversationRuntimeBinding<'_>) -> CliResult<AssembledConversationContext>
method on the HostedConversationRuntime impl that simply calls and returns
self.inner.build_context(...) so the inner runtime (e.g.,
DefaultConversationRuntime) preserves its assembled-context (prompt
fragments/metadata) behavior rather than being rebuilt from build_messages().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 224be94c-9632-46d8-ad7d-7ac7ef8dab04

📥 Commits

Reviewing files that changed from the base of the PR and between f22cd1d and 0f4c2f7.

📒 Files selected for processing (3)
  • crates/app/src/conversation/mod.rs
  • crates/app/src/conversation/runtime.rs
  • crates/daemon/src/tasks_cli.rs

Comment on lines +1479 to +1668
#[cfg(feature = "memory-sqlite")]
#[async_trait]
impl<R> ConversationRuntime for HostedConversationRuntime<R>
where
R: ConversationRuntime,
{
fn session_context(
&self,
config: &LoongClawConfig,
session_id: &str,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<SessionContext> {
self.inner.session_context(config, session_id, binding)
}

fn tool_view(
&self,
config: &LoongClawConfig,
session_id: &str,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<ToolView> {
self.inner.tool_view(config, session_id, binding)
}

fn async_delegate_spawner(
&self,
config: &LoongClawConfig,
) -> Option<Arc<dyn AsyncDelegateSpawner>> {
let override_spawner = self.async_delegate_spawner_override.clone();
match override_spawner {
Some(override_spawner) => Some(override_spawner),
None => self.inner.async_delegate_spawner(config),
}
}

fn background_task_spawner(
&self,
config: &LoongClawConfig,
) -> Option<Arc<dyn AsyncDelegateSpawner>> {
let override_spawner = self.background_task_spawner_override.clone();
match override_spawner {
Some(override_spawner) => Some(override_spawner),
None => self.inner.background_task_spawner(config),
}
}

async fn bootstrap(
&self,
config: &LoongClawConfig,
session_id: &str,
kernel_ctx: &KernelContext,
) -> CliResult<ContextEngineBootstrapResult> {
self.inner.bootstrap(config, session_id, kernel_ctx).await
}

async fn ingest(
&self,
session_id: &str,
message: &Value,
kernel_ctx: &KernelContext,
) -> CliResult<ContextEngineIngestResult> {
self.inner.ingest(session_id, message, kernel_ctx).await
}

async fn build_messages(
&self,
config: &LoongClawConfig,
session_id: &str,
include_system_prompt: bool,
tool_view: &ToolView,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<Vec<Value>> {
self.inner
.build_messages(
config,
session_id,
include_system_prompt,
tool_view,
binding,
)
.await
}

async fn request_completion(
&self,
config: &LoongClawConfig,
messages: &[Value],
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<String> {
self.inner
.request_completion(config, messages, binding)
.await
}

async fn request_turn(
&self,
config: &LoongClawConfig,
session_id: &str,
turn_id: &str,
messages: &[Value],
tool_view: &ToolView,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<ProviderTurn> {
self.inner
.request_turn(config, session_id, turn_id, messages, tool_view, binding)
.await
}

async fn request_turn_streaming(
&self,
config: &LoongClawConfig,
session_id: &str,
turn_id: &str,
messages: &[Value],
tool_view: &ToolView,
binding: ConversationRuntimeBinding<'_>,
on_token: crate::provider::StreamingTokenCallback,
) -> CliResult<ProviderTurn> {
self.inner
.request_turn_streaming(
config, session_id, turn_id, messages, tool_view, binding, on_token,
)
.await
}

async fn persist_turn(
&self,
session_id: &str,
role: &str,
content: &str,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<()> {
self.inner
.persist_turn(session_id, role, content, binding)
.await
}

async fn after_turn(
&self,
session_id: &str,
user_input: &str,
assistant_reply: &str,
messages: &[Value],
kernel_ctx: &KernelContext,
) -> CliResult<()> {
self.inner
.after_turn(
session_id,
user_input,
assistant_reply,
messages,
kernel_ctx,
)
.await
}

async fn compact_context(
&self,
config: &LoongClawConfig,
session_id: &str,
messages: &[Value],
kernel_ctx: &KernelContext,
) -> CliResult<()> {
self.inner
.compact_context(config, session_id, messages, kernel_ctx)
.await
}

async fn prepare_subagent_spawn(
&self,
parent_session_id: &str,
subagent_session_id: &str,
kernel_ctx: &KernelContext,
) -> CliResult<()> {
self.inner
.prepare_subagent_spawn(parent_session_id, subagent_session_id, kernel_ctx)
.await
}

async fn on_subagent_ended(
&self,
parent_session_id: &str,
subagent_session_id: &str,
kernel_ctx: &KernelContext,
) -> CliResult<()> {
self.inner
.on_subagent_ended(parent_session_id, subagent_session_id, kernel_ctx)
.await
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Delegate build_context explicitly.

HostedConversationRuntime currently falls back to the trait’s default build_context(), so it does not preserve an inner runtime’s custom build_context() behavior. For DefaultConversationRuntime, that means the wrapper rebuilds AssembledConversationContext from build_messages() and drops assembled-context data like prompt fragments/metadata. That breaks the “delegate the full runtime trait” contract.

Proposed fix
 #[cfg(feature = "memory-sqlite")]
 #[async_trait]
 impl<R> ConversationRuntime for HostedConversationRuntime<R>
 where
     R: ConversationRuntime,
 {
@@
     async fn ingest(
         &self,
         session_id: &str,
         message: &Value,
         kernel_ctx: &KernelContext,
     ) -> CliResult<ContextEngineIngestResult> {
         self.inner.ingest(session_id, message, kernel_ctx).await
     }
+
+    async fn build_context(
+        &self,
+        config: &LoongClawConfig,
+        session_id: &str,
+        include_system_prompt: bool,
+        binding: ConversationRuntimeBinding<'_>,
+    ) -> CliResult<AssembledConversationContext> {
+        self.inner
+            .build_context(config, session_id, include_system_prompt, binding)
+            .await
+    }
 
     async fn build_messages(
         &self,
         config: &LoongClawConfig,
         session_id: &str,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/conversation/runtime.rs` around lines 1479 - 1668, The
HostedConversationRuntime impl must explicitly delegate build_context to the
inner runtime instead of using the trait default; add a build_context(&self,
config: &LoongClawConfig, session_id: &str, include_system_prompt: bool,
tool_view: &ToolView, binding: ConversationRuntimeBinding<'_>) ->
CliResult<AssembledConversationContext> method on the HostedConversationRuntime
impl that simply calls and returns self.inner.build_context(...) so the inner
runtime (e.g., DefaultConversationRuntime) preserves its assembled-context
(prompt fragments/metadata) behavior rather than being rebuilt from
build_messages().

Comment on lines +2198 to +2225
#[cfg(feature = "memory-sqlite")]
#[test]
fn hosted_runtime_overrides_background_task_spawner_without_changing_async_delegate_spawner() {
let config = LoongClawConfig::default();
let inner_async_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let override_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_runtime = SpawnerAwareRuntime {
async_delegate_spawner: Some(inner_async_spawner.clone()),
background_task_spawner: Some(inner_background_spawner),
};

let hosted_runtime = HostedConversationRuntime::new(inner_runtime)
.with_background_task_spawner(override_background_spawner.clone());

let resolved_async_spawner = hosted_runtime
.async_delegate_spawner(&config)
.expect("async delegate spawner");
let resolved_background_spawner = hosted_runtime
.background_task_spawner(&config)
.expect("background task spawner");

assert!(Arc::ptr_eq(&resolved_async_spawner, &inner_async_spawner));
assert!(Arc::ptr_eq(
&resolved_background_spawner,
&override_background_spawner
));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add the symmetric regression for with_async_delegate_spawner.

This only locks down the background override path. with_async_delegate_spawner() can regress independently, so the new hosted wrapper still has an uncovered override seam.

Suggested follow-up test shape
+    #[cfg(feature = "memory-sqlite")]
+    #[test]
+    fn hosted_runtime_overrides_async_delegate_spawner_without_changing_background_task_spawner() {
+        let config = LoongClawConfig::default();
+        let override_async_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
+        let inner_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
+        let inner_runtime = SpawnerAwareRuntime {
+            async_delegate_spawner: None,
+            background_task_spawner: Some(inner_background_spawner.clone()),
+        };
+
+        let hosted_runtime = HostedConversationRuntime::new(inner_runtime)
+            .with_async_delegate_spawner(override_async_spawner.clone());
+
+        let resolved_async_spawner = hosted_runtime
+            .async_delegate_spawner(&config)
+            .expect("async delegate spawner");
+        let resolved_background_spawner = hosted_runtime
+            .background_task_spawner(&config)
+            .expect("background task spawner");
+
+        assert!(Arc::ptr_eq(&resolved_async_spawner, &override_async_spawner));
+        assert!(Arc::ptr_eq(
+            &resolved_background_spawner,
+            &inner_background_spawner
+        ));
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[cfg(feature = "memory-sqlite")]
#[test]
fn hosted_runtime_overrides_background_task_spawner_without_changing_async_delegate_spawner() {
let config = LoongClawConfig::default();
let inner_async_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let override_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_runtime = SpawnerAwareRuntime {
async_delegate_spawner: Some(inner_async_spawner.clone()),
background_task_spawner: Some(inner_background_spawner),
};
let hosted_runtime = HostedConversationRuntime::new(inner_runtime)
.with_background_task_spawner(override_background_spawner.clone());
let resolved_async_spawner = hosted_runtime
.async_delegate_spawner(&config)
.expect("async delegate spawner");
let resolved_background_spawner = hosted_runtime
.background_task_spawner(&config)
.expect("background task spawner");
assert!(Arc::ptr_eq(&resolved_async_spawner, &inner_async_spawner));
assert!(Arc::ptr_eq(
&resolved_background_spawner,
&override_background_spawner
));
}
#[cfg(feature = "memory-sqlite")]
#[test]
fn hosted_runtime_overrides_background_task_spawner_without_changing_async_delegate_spawner() {
let config = LoongClawConfig::default();
let inner_async_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let override_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_runtime = SpawnerAwareRuntime {
async_delegate_spawner: Some(inner_async_spawner.clone()),
background_task_spawner: Some(inner_background_spawner),
};
let hosted_runtime = HostedConversationRuntime::new(inner_runtime)
.with_background_task_spawner(override_background_spawner.clone());
let resolved_async_spawner = hosted_runtime
.async_delegate_spawner(&config)
.expect("async delegate spawner");
let resolved_background_spawner = hosted_runtime
.background_task_spawner(&config)
.expect("background task spawner");
assert!(Arc::ptr_eq(&resolved_async_spawner, &inner_async_spawner));
assert!(Arc::ptr_eq(
&resolved_background_spawner,
&override_background_spawner
));
}
#[cfg(feature = "memory-sqlite")]
#[test]
fn hosted_runtime_overrides_async_delegate_spawner_without_changing_background_task_spawner() {
let config = LoongClawConfig::default();
let override_async_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
let inner_runtime = SpawnerAwareRuntime {
async_delegate_spawner: None,
background_task_spawner: Some(inner_background_spawner.clone()),
};
let hosted_runtime = HostedConversationRuntime::new(inner_runtime)
.with_async_delegate_spawner(override_async_spawner.clone());
let resolved_async_spawner = hosted_runtime
.async_delegate_spawner(&config)
.expect("async delegate spawner");
let resolved_background_spawner = hosted_runtime
.background_task_spawner(&config)
.expect("background task spawner");
assert!(Arc::ptr_eq(&resolved_async_spawner, &override_async_spawner));
assert!(Arc::ptr_eq(
&resolved_background_spawner,
&inner_background_spawner
));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/conversation/runtime.rs` around lines 2198 - 2225, Add a
symmetric test that verifies with_async_delegate_spawner() overrides the async
delegate spawner without affecting the background task spawner: create a test
(e.g.,
hosted_runtime_overrides_async_delegate_spawner_without_changing_background_task_spawner)
mirroring the existing hosted_runtime_overrides_background_task_spawner... test
but call
HostedConversationRuntime::new(inner_runtime).with_async_delegate_spawner(override_async_spawner),
then resolve both hosted_runtime.async_delegate_spawner(&config) and
hosted_runtime.background_task_spawner(&config) and assert Arc::ptr_eq that the
async resolver returns the override_async_spawner while the background resolver
still returns the original inner_background_spawner; use the same types/fixtures
(SpawnerAwareRuntime, LoongClawConfig, NoopTestSpawner) so the test covers the
missing override seam.

@github-actions github-actions Bot added tools Tool runtime, policy adapters, and tool catalog behavior. size: L Large pull request: 501-1000 changed lines. and removed size: M Medium pull request: 201-500 changed lines. labels Apr 10, 2026
@chumyin
Copy link
Copy Markdown
Collaborator Author

chumyin commented Apr 10, 2026

Follow-up pushed in f914095dd.

Added the next narrow runtime-truth slice on top of the hosted-runtime foundation:

  • explicit owner_kind in constrained subagent execution metadata
  • async delegates now stamp async_delegate_spawner
  • background tasks now stamp background_task_host
  • task payloads and text rendering surface the owner kind directly

Revalidated locally after the follow-up:

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --locked -- --test-threads=1
  • cargo test --workspace --all-features --locked -- --test-threads=1
  • ./scripts/check_architecture_boundaries.sh
  • ./scripts/check_dep_graph.sh

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/app/src/conversation/turn_coordinator.rs (1)

7917-7937: ⚠️ Potential issue | 🟡 Minor

Use a production-valid owner_kind in the async recovery fixtures.

build_delegate_async_enqueue_request() now always stamps queued async delegates with Some(ConstrainedSubagentOwnerKind::AsyncDelegateSpawner). Keeping these async fixtures at None means these recovery tests no longer exercise the state that new async rows actually persist, which weakens coverage for owner-kind propagation and any terminal rendering built on it.

♻️ Suggested adjustment
-            owner_kind: None,
+            owner_kind: Some(ConstrainedSubagentOwnerKind::AsyncDelegateSpawner),
...
-            owner_kind: None,
+            owner_kind: Some(ConstrainedSubagentOwnerKind::AsyncDelegateSpawner),

Also applies to: 8050-8070

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/conversation/turn_coordinator.rs` around lines 7917 - 7937,
The async recovery fixture uses ConstrainedSubagentExecution with owner_kind set
to None, but async delegates are now stamped with
Some(ConstrainedSubagentOwnerKind::AsyncDelegateSpawner); update the fixture(s)
(e.g., in build_delegate_async_enqueue_request and the
ConstrainedSubagentExecution instances around ConstrainedSubagentMode::Async at
the shown locations) to set owner_kind:
Some(crate::conversation::ConstrainedSubagentOwnerKind::AsyncDelegateSpawner) so
tests persist the actual owner-kind used in production (also apply the same
change to the similar fixtures at the other range).
🧹 Nitpick comments (2)
crates/app/src/operator/delegate_runtime.rs (1)

715-745: Add one positive owner_kind propagation test.

The updated fixtures all use owner_kind: None, so they only prove the new field is optional. A single Some(...) case that asserts both seed.execution.owner_kind and the serialized lifecycle payload would lock down the new contract and catch silent drops.

Also applies to: 750-777, 886-906

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/operator/delegate_runtime.rs` around lines 715 - 745, Add a
positive test that sets owner_kind = Some("<kind>".to_owned()) on the
DelegateChildExecutionPolicy passed into build_delegate_child_lifecycle_seed and
assert that the returned seed has seed.execution.owner_kind ==
Some("<kind>".to_owned()); also assert the serialized lifecycle payload (the
lifecycle/request blob produced by the seed) contains the same owner_kind value
to ensure it is propagated into the serialized message. Target the existing test
blocks around build_delegate_child_lifecycle_seed / DelegateChildExecutionPolicy
(the seed variable and seed.execution fields) and mirror the style of the
current assertions so the new test covers owner_kind propagation for the Async
case (and repeat similarly for the other test locations noted).
crates/app/src/conversation/subagent.rs (1)

651-656: Optional: also lock string mapping for AsyncDelegateSpawner.

You already verify "background_task_host"; adding the second variant helps guard future regressions in literal mapping.

✅ Optional test addition
         assert_eq!(
             ConstrainedSubagentExecution::from_event_payload(&payload)
                 .and_then(|execution| execution.owner_kind)
                 .map(ConstrainedSubagentOwnerKind::as_str),
             Some("background_task_host")
         );
+        assert_eq!(
+            ConstrainedSubagentOwnerKind::AsyncDelegateSpawner.as_str(),
+            "async_delegate_spawner"
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/conversation/subagent.rs` around lines 651 - 656, Add a second
assertion that locks the string mapping for the AsyncDelegateSpawner variant:
mirror the existing check by calling
ConstrainedSubagentExecution::from_event_payload(&payload).and_then(|execution|
execution.owner_kind).map(ConstrainedSubagentOwnerKind::as_str) and assert it
equals Some("async_delegate_spawner") (using
ConstrainedSubagentOwnerKind::AsyncDelegateSpawner as the variant reference) so
the literal mapping is validated alongside "background_task_host".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/app/src/conversation/turn_coordinator.rs`:
- Around line 7917-7937: The async recovery fixture uses
ConstrainedSubagentExecution with owner_kind set to None, but async delegates
are now stamped with Some(ConstrainedSubagentOwnerKind::AsyncDelegateSpawner);
update the fixture(s) (e.g., in build_delegate_async_enqueue_request and the
ConstrainedSubagentExecution instances around ConstrainedSubagentMode::Async at
the shown locations) to set owner_kind:
Some(crate::conversation::ConstrainedSubagentOwnerKind::AsyncDelegateSpawner) so
tests persist the actual owner-kind used in production (also apply the same
change to the similar fixtures at the other range).

---

Nitpick comments:
In `@crates/app/src/conversation/subagent.rs`:
- Around line 651-656: Add a second assertion that locks the string mapping for
the AsyncDelegateSpawner variant: mirror the existing check by calling
ConstrainedSubagentExecution::from_event_payload(&payload).and_then(|execution|
execution.owner_kind).map(ConstrainedSubagentOwnerKind::as_str) and assert it
equals Some("async_delegate_spawner") (using
ConstrainedSubagentOwnerKind::AsyncDelegateSpawner as the variant reference) so
the literal mapping is validated alongside "background_task_host".

In `@crates/app/src/operator/delegate_runtime.rs`:
- Around line 715-745: Add a positive test that sets owner_kind =
Some("<kind>".to_owned()) on the DelegateChildExecutionPolicy passed into
build_delegate_child_lifecycle_seed and assert that the returned seed has
seed.execution.owner_kind == Some("<kind>".to_owned()); also assert the
serialized lifecycle payload (the lifecycle/request blob produced by the seed)
contains the same owner_kind value to ensure it is propagated into the
serialized message. Target the existing test blocks around
build_delegate_child_lifecycle_seed / DelegateChildExecutionPolicy (the seed
variable and seed.execution fields) and mirror the style of the current
assertions so the new test covers owner_kind propagation for the Async case (and
repeat similarly for the other test locations noted).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7551b04e-534c-4e46-8403-92261d7a23b1

📥 Commits

Reviewing files that changed from the base of the PR and between 0f4c2f7 and f914095.

📒 Files selected for processing (9)
  • crates/app/src/conversation/mod.rs
  • crates/app/src/conversation/subagent.rs
  • crates/app/src/conversation/tests.rs
  • crates/app/src/conversation/turn_coordinator.rs
  • crates/app/src/conversation/workspace_isolation.rs
  • crates/app/src/operator/delegate_runtime.rs
  • crates/app/src/tools/runtime_config.rs
  • crates/daemon/src/tasks_cli.rs
  • crates/daemon/tests/integration/tasks_cli.rs

chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 10, 2026
Default conversation runtime construction was still scattered across
coordinator, turn loop, channel dispatch, delegate support, turn
engine, and daemon task bootstrap sites. That drift weakened the
HostedConversationRuntime seam and made future runtime-host work more
likely to fork behavior.

This change introduces one shared default-runtime loader in the
conversation runtime module, adds a hosted-default loader for runtime
host composition, and routes the remaining app/daemon bootstrap sites
through those helpers. Observer failure signaling remains intact while
background-task runtime composition now reuses the same bootstrap path.

Constraint: Preserve existing context-engine selection and observer failure behavior
Rejected: Introduce a larger runtime factory trait now | unnecessary scope for this convergence slice
Rejected: Add a global cached runtime singleton | premature before lifecycle ownership is explicit
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: New app/daemon default runtime bootstrap sites should use load_default_conversation_runtime or load_hosted_default_conversation_runtime instead of calling from_config_or_env directly
Tested: cargo fmt --all -- --check
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No new benchmark or latency measurement in this refactor-only slice
Related: eastreams#1198
Related: eastreams#1194
@github-actions github-actions Bot added the channels Channel adapters and external integration surfaces. label Apr 10, 2026
@chumyin
Copy link
Copy Markdown
Collaborator Author

chumyin commented Apr 10, 2026

Follow-up pushed for the runtime-host convergence lane.

New commit: 2caa2390b (Converge default conversation runtime bootstrap behind one host seam)

What changed in this PR update:

  • added shared default runtime loader helpers in crates/app/src/conversation/runtime.rs
  • switched remaining app/daemon bootstrap sites to the shared helpers
  • reused the shared hosted-default loader in crates/daemon/src/tasks_cli.rs
  • kept observer failure behavior intact in ConversationTurnCoordinator
  • reinforced regression coverage for the hosted default loader seam

Verification rerun after the follow-up:

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --locked -- --test-threads=1
  • cargo test --workspace --all-features --locked -- --test-threads=1
  • ./scripts/check_architecture_boundaries.sh
  • ./scripts/check_dep_graph.sh

Related issue: #1198

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
crates/app/src/conversation/runtime.rs (2)

2216-2257: ⚠️ Potential issue | 🟡 Minor

Add the symmetric regression test for with_async_delegate_spawner.

Current tests cover background override and hosted-default loader behavior, but the async override seam is still unguarded. Please add a mirror test to verify async override precedence without changing background behavior.

Suggested test addition
+    #[cfg(feature = "memory-sqlite")]
+    #[test]
+    fn hosted_runtime_overrides_async_delegate_spawner_without_changing_background_task_spawner() {
+        let config = LoongClawConfig::default();
+        let override_async_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
+        let inner_background_spawner: Arc<dyn AsyncDelegateSpawner> = Arc::new(NoopTestSpawner);
+        let inner_runtime = SpawnerAwareRuntime {
+            async_delegate_spawner: None,
+            background_task_spawner: Some(inner_background_spawner.clone()),
+        };
+
+        let hosted_runtime = HostedConversationRuntime::new(inner_runtime)
+            .with_async_delegate_spawner(override_async_spawner.clone());
+
+        let resolved_async_spawner = hosted_runtime
+            .async_delegate_spawner(&config)
+            .expect("async delegate spawner");
+        let resolved_background_spawner = hosted_runtime
+            .background_task_spawner(&config)
+            .expect("background task spawner");
+
+        assert!(Arc::ptr_eq(&resolved_async_spawner, &override_async_spawner));
+        assert!(Arc::ptr_eq(
+            &resolved_background_spawner,
+            &inner_background_spawner
+        ));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/conversation/runtime.rs` around lines 2216 - 2257, Add a
symmetric unit test that mirrors
hosted_runtime_overrides_background_task_spawner_without_changing_async_delegate_spawner
but exercises with_async_delegate_spawner: construct an inner
SpawnerAwareRuntime with both async_delegate_spawner and background_task_spawner
set (use NoopTestSpawner), create
HostedConversationRuntime::new(inner_runtime).with_async_delegate_spawner(override_async_spawner),
then call hosted_runtime.async_delegate_spawner(&config) and
hosted_runtime.background_task_spawner(&config) and assert that the resolved
async spawner is the override (Arc::ptr_eq to override_async_spawner) while the
background spawner remains the original inner background spawner; use the same
config, types, and naming conventions as the existing tests.

1497-1686: ⚠️ Potential issue | 🟠 Major

Delegate build_context explicitly in the hosted wrapper.

In this impl block (starting Line 1499), build_context is not overridden, so the trait default at Line 1394 is used. That bypasses inner-runtime custom build_context behavior and can drop assembled-context details (e.g., prompt fragments/metadata) for runtimes like DefaultConversationRuntime.

Proposed fix
 #[cfg(feature = "memory-sqlite")]
 #[async_trait]
 impl<R> ConversationRuntime for HostedConversationRuntime<R>
 where
     R: ConversationRuntime,
 {
@@
     async fn ingest(
         &self,
         session_id: &str,
         message: &Value,
         kernel_ctx: &KernelContext,
     ) -> CliResult<ContextEngineIngestResult> {
         self.inner.ingest(session_id, message, kernel_ctx).await
     }
+
+    async fn build_context(
+        &self,
+        config: &LoongClawConfig,
+        session_id: &str,
+        include_system_prompt: bool,
+        binding: ConversationRuntimeBinding<'_>,
+    ) -> CliResult<AssembledConversationContext> {
+        self.inner
+            .build_context(config, session_id, include_system_prompt, binding)
+            .await
+    }
 
     async fn build_messages(
         &self,
         config: &LoongClawConfig,
         session_id: &str,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/conversation/runtime.rs` around lines 1497 - 1686, The
HostedConversationRuntime<R> impl is missing an override for build_context so it
uses the trait default and bypasses inner runtime behavior; add an async fn
build_context with the same signature as the trait and simply delegate to and
return self.inner.build_context(...).await (i.e., implement build_context on
HostedConversationRuntime to call into self.inner.build_context with the same
params so runtimes like DefaultConversationRuntime retain their
assembled-context details).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/app/src/conversation/runtime.rs`:
- Around line 2216-2257: Add a symmetric unit test that mirrors
hosted_runtime_overrides_background_task_spawner_without_changing_async_delegate_spawner
but exercises with_async_delegate_spawner: construct an inner
SpawnerAwareRuntime with both async_delegate_spawner and background_task_spawner
set (use NoopTestSpawner), create
HostedConversationRuntime::new(inner_runtime).with_async_delegate_spawner(override_async_spawner),
then call hosted_runtime.async_delegate_spawner(&config) and
hosted_runtime.background_task_spawner(&config) and assert that the resolved
async spawner is the override (Arc::ptr_eq to override_async_spawner) while the
background spawner remains the original inner background spawner; use the same
config, types, and naming conventions as the existing tests.
- Around line 1497-1686: The HostedConversationRuntime<R> impl is missing an
override for build_context so it uses the trait default and bypasses inner
runtime behavior; add an async fn build_context with the same signature as the
trait and simply delegate to and return self.inner.build_context(...).await
(i.e., implement build_context on HostedConversationRuntime to call into
self.inner.build_context with the same params so runtimes like
DefaultConversationRuntime retain their assembled-context details).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f8320ac8-883b-41f4-b2f2-31387f4540df

📥 Commits

Reviewing files that changed from the base of the PR and between f914095 and 2caa239.

📒 Files selected for processing (9)
  • crates/app/src/channel/dispatch.rs
  • crates/app/src/conversation/delegate_support.rs
  • crates/app/src/conversation/mod.rs
  • crates/app/src/conversation/runtime.rs
  • crates/app/src/conversation/tests.rs
  • crates/app/src/conversation/turn_coordinator.rs
  • crates/app/src/conversation/turn_engine.rs
  • crates/app/src/conversation/turn_loop.rs
  • crates/daemon/src/tasks_cli.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/app/src/conversation/tests.rs
  • crates/app/src/conversation/mod.rs

chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 11, 2026
Provider request sessions and model-catalog fetches were still building a
fresh reqwest client on every call. That left one of the remaining
always-on/runtime-host seams unnecessarily short-lived even after the
conversation runtime bootstrap convergence work.

This change adds a small host-owned provider HTTP client cache keyed by
the transport policy field that currently affects client construction and
routes both request-session and model-catalog paths through the shared
helper. Auth headers and auth-context lifetimes remain request-scoped, so
transport reuse does not widen credential lifetime.

Constraint: Keep auth headers, auth context, and retry state request-scoped while reusing only transport clients
Rejected: Cache full provider request sessions | would mix transport reuse with auth/model-candidate lifecycle
Rejected: Add benchmark plumbing in the same slice | unnecessary scope for a correctness-first host convergence step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If future provider transport options affect reqwest client construction, extend ProviderHttpClientCacheKey instead of bypassing the shared cache
Tested: cargo fmt --all -- --check
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No dedicated TTFT benchmark yet; this slice proves cache reuse and preserves behavior only
Related: eastreams#1202
Related: eastreams#1194
@github-actions github-actions Bot added the providers Provider routing, selection, and transport behavior. label Apr 11, 2026
@chumyin
Copy link
Copy Markdown
Collaborator Author

chumyin commented Apr 11, 2026

Another runtime-host follow-up is now pushed onto this PR.

New commit: f083d3de8 (Reduce provider transport cold-start churn in the runtime host path)

What changed in this PR update:

  • added a host-owned provider HTTP client cache in crates/app/src/provider/http_client_runtime.rs
  • reused cached reqwest clients across provider request-session and model-catalog paths
  • kept auth headers/auth context request-scoped so transport reuse does not widen credential lifetime
  • added focused cache-reuse regressions for the provider transport seam

Verification rerun after the follow-up:

  • cargo fmt --all -- --check
  • cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
  • cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --locked -- --test-threads=1
  • cargo test --workspace --all-features --locked -- --test-threads=1
  • ./scripts/check_architecture_boundaries.sh
  • ./scripts/check_dep_graph.sh

Related issue: #1202

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/app/src/provider/http_client_runtime.rs (1)

23-25: Consider bounding cache growth for long-lived processes.

Line 24 and Line 98 create a process-wide cache with no eviction/upper bound. If timeout values vary widely at runtime, key cardinality can grow without limit.

Also applies to: 98-99

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/app/src/provider/http_client_runtime.rs`:
- Around line 88-94: The cache miss path is not atomic: concurrent callers can
both call build_provider_http_client for the same cache_key; fix by implementing
an atomic get-or-build for provider clients — e.g., use a per-key lock or the
map's entry API/once_cell semantics: call
load_cached_provider_http_client(cache_key), and on miss acquire a shared
per-key mutex (or use cache.entry(cache_key).or_insert_with), re-check the cache
inside the lock, then call build_provider_http_client(cache_key) only if still
missing, store via store_provider_http_client(cache_key, built_client), release
the lock and return the cached client; ensure the functions
load_cached_provider_http_client, build_provider_http_client and
store_provider_http_client are used in this double-checked locking / entry-based
flow so duplicate builds are prevented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d547723-34f4-43f7-a852-8ca8b849d0d1

📥 Commits

Reviewing files that changed from the base of the PR and between 2caa239 and f083d3d.

📒 Files selected for processing (1)
  • crates/app/src/provider/http_client_runtime.rs

Comment on lines +88 to +94
if let Some(cached_client) = load_cached_provider_http_client(cache_key) {
return Ok(cached_client);
}

let built_client = build_provider_http_client(cache_key)?;
let cached_client = store_provider_http_client(cache_key, built_client);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Non-atomic cache miss path allows duplicate client builds under contention.

Line 88 through Line 94 split cache read/build/write across separate critical sections. Two concurrent misses for the same key can both execute build_provider_http_client, so cold-start churn still happens.

Suggested fix (atomic get-or-build path)
+fn load_or_build_provider_http_client(
+    cache_key: ProviderHttpClientCacheKey,
+) -> CliResult<reqwest::Client> {
+    with_provider_http_client_cache(|cache| {
+        if let Some(cached_client) = cache.entries.get(&cache_key) {
+            return Ok(cached_client.clone());
+        }
+
+        let built_client = build_provider_http_client(cache_key)?;
+        cache.entries.insert(cache_key, built_client.clone());
+        Ok(built_client)
+    })
+}
+
 pub(super) fn build_http_client(
     request_policy: &policy::ProviderRequestPolicy,
 ) -> CliResult<reqwest::Client> {
     let cache_key = ProviderHttpClientCacheKey::from_request_policy(request_policy);
-
-    if let Some(cached_client) = load_cached_provider_http_client(cache_key) {
-        return Ok(cached_client);
-    }
-
-    let built_client = build_provider_http_client(cache_key)?;
-    let cached_client = store_provider_http_client(cache_key, built_client);
-
-    Ok(cached_client)
+    load_or_build_provider_http_client(cache_key)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/src/provider/http_client_runtime.rs` around lines 88 - 94, The
cache miss path is not atomic: concurrent callers can both call
build_provider_http_client for the same cache_key; fix by implementing an atomic
get-or-build for provider clients — e.g., use a per-key lock or the map's entry
API/once_cell semantics: call load_cached_provider_http_client(cache_key), and
on miss acquire a shared per-key mutex (or use
cache.entry(cache_key).or_insert_with), re-check the cache inside the lock, then
call build_provider_http_client(cache_key) only if still missing, store via
store_provider_http_client(cache_key, built_client), release the lock and return
the cached client; ensure the functions load_cached_provider_http_client,
build_provider_http_client and store_provider_http_client are used in this
double-checked locking / entry-based flow so duplicate builds are prevented.

chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 11, 2026
LoongClaw's streaming turns already emitted token deltas, but the
runtime had no direct first-token signal for operators or live surfaces.
That made TTFT regressions hard to see while runtime-host work was
converging around longer-lived transport and bootstrap seams.

This change adds additive elapsed timing metadata to streaming token
observer events, stamps it from provider-request start in the
coordinator, and threads the first-token latency into the live chat
surface so active turns can show TTFT without changing provider
semantics or persistence contracts.

Constraint: Keep the change additive and backward-compatible for existing streaming event consumers
Rejected: Add a separate telemetry subsystem first | broader than the next safe maturity slice
Rejected: Measure only end-to-end request duration | misses the first-token latency signal needed for interactive runtime tuning
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Future streaming observer consumers should treat elapsed_ms as optional metadata and avoid assuming every provider path can produce TTFT
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib map_streaming_callback_data_to_token_event_ -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib handle_turn_with_observer_uses_streaming_request_and_emits_live_events -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib render_cli_chat_live_surface_lines_show_pipeline_status_and_preview -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_emits_phase_and_stream_preview_batches -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_resets_request_scoped_buffers_between_rounds -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No separate benchmark harness or persisted TTFT analytics in this slice
Related: eastreams#1203
Related: eastreams#1194
@github-actions github-actions Bot added the acp ACP manager, binding, routing, and control plane surfaces. label Apr 11, 2026
@chumyin
Copy link
Copy Markdown
Collaborator Author

chumyin commented Apr 11, 2026

Another runtime-host maturity follow-up is now pushed onto this PR.

New commit: 042e6cfc5 (Make first-token latency visible in the runtime observer path)

What changed in this PR update:

  • added additive elapsed_ms metadata to streaming token events
  • stamped observer token events from provider-request start in the coordinator
  • surfaced first-token latency in live runtime rendering for active turns
  • kept the timing metadata optional so existing event consumers remain compatible
  • added focused regressions for TTFT event-shape and live-surface reset/render behavior

Verification rerun after the follow-up:

  • cargo fmt --all -- --check
  • cargo test -p loongclaw-app --lib map_streaming_callback_data_to_token_event_ -- --test-threads=1
  • cargo test -p loongclaw-app --lib handle_turn_with_observer_uses_streaming_request_and_emits_live_events -- --test-threads=1
  • cargo test -p loongclaw-app --lib render_cli_chat_live_surface_lines_show_pipeline_status_and_preview -- --test-threads=1
  • cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_emits_phase_and_stream_preview_batches -- --test-threads=1
  • cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_resets_request_scoped_buffers_between_rounds -- --test-threads=1
  • cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --locked -- --test-threads=1
  • cargo test --workspace --all-features --locked -- --test-threads=1
  • ./scripts/check_architecture_boundaries.sh
  • ./scripts/check_dep_graph.sh

Related issue: #1203

chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 11, 2026
LoongClaw's runtime host path can now reuse provider HTTP clients, but
that cache was still opaque to runtime snapshots and operator read
models. Without an observable transport-host signal, it remained hard to
confirm whether the cache was warm or whether transport churn had
regressed.

This change adds additive provider transport cache metrics in the app
runtime layer and threads them into daemon runtime snapshot state plus
JSON/text rendering. The cache remains in-process only and request
semantics stay unchanged; this is strictly observability over the
host-owned transport seam.

Constraint: Keep transport metrics additive and observational without changing request/auth semantics
Rejected: Leave the cache opaque and rely on profiling only | makes always-on regressions harder to diagnose in normal operator flows
Rejected: Add a larger telemetry pipeline first | broader than the next safe observability step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If new reqwest-client construction knobs are added, update both ProviderHttpClientCacheKey and the exported runtime metrics so snapshot truth stays aligned with actual cache behavior
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_json_payload_includes_provider_tool_and_external_skill_inventory -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_text_highlights_experiment_relevant_sections -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No cross-process transport cache sharing or benchmark harness in this slice
Related: eastreams#1204
Related: eastreams#1194
@github-actions github-actions Bot added size: XL Very large pull request: more than 1000 changed lines. and removed size: L Large pull request: 501-1000 changed lines. labels Apr 11, 2026
@chumyin chumyin requested a review from a team as a code owner April 17, 2026 06:06
@github-actions github-actions Bot added the documentation Improvements or additions to documentation. label Apr 17, 2026
chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 17, 2026
Default conversation runtime construction was still scattered across
coordinator, turn loop, channel dispatch, delegate support, turn
engine, and daemon task bootstrap sites. That drift weakened the
HostedConversationRuntime seam and made future runtime-host work more
likely to fork behavior.

This change introduces one shared default-runtime loader in the
conversation runtime module, adds a hosted-default loader for runtime
host composition, and routes the remaining app/daemon bootstrap sites
through those helpers. Observer failure signaling remains intact while
background-task runtime composition now reuses the same bootstrap path.

Constraint: Preserve existing context-engine selection and observer failure behavior
Rejected: Introduce a larger runtime factory trait now | unnecessary scope for this convergence slice
Rejected: Add a global cached runtime singleton | premature before lifecycle ownership is explicit
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: New app/daemon default runtime bootstrap sites should use load_default_conversation_runtime or load_hosted_default_conversation_runtime instead of calling from_config_or_env directly
Tested: cargo fmt --all -- --check
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No new benchmark or latency measurement in this refactor-only slice
Related: eastreams#1198
Related: eastreams#1194
chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 17, 2026
Provider request sessions and model-catalog fetches were still building a
fresh reqwest client on every call. That left one of the remaining
always-on/runtime-host seams unnecessarily short-lived even after the
conversation runtime bootstrap convergence work.

This change adds a small host-owned provider HTTP client cache keyed by
the transport policy field that currently affects client construction and
routes both request-session and model-catalog paths through the shared
helper. Auth headers and auth-context lifetimes remain request-scoped, so
transport reuse does not widen credential lifetime.

Constraint: Keep auth headers, auth context, and retry state request-scoped while reusing only transport clients
Rejected: Cache full provider request sessions | would mix transport reuse with auth/model-candidate lifecycle
Rejected: Add benchmark plumbing in the same slice | unnecessary scope for a correctness-first host convergence step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If future provider transport options affect reqwest client construction, extend ProviderHttpClientCacheKey instead of bypassing the shared cache
Tested: cargo fmt --all -- --check
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No dedicated TTFT benchmark yet; this slice proves cache reuse and preserves behavior only
Related: eastreams#1202
Related: eastreams#1194
chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 17, 2026
LoongClaw's streaming turns already emitted token deltas, but the
runtime had no direct first-token signal for operators or live surfaces.
That made TTFT regressions hard to see while runtime-host work was
converging around longer-lived transport and bootstrap seams.

This change adds additive elapsed timing metadata to streaming token
observer events, stamps it from provider-request start in the
coordinator, and threads the first-token latency into the live chat
surface so active turns can show TTFT without changing provider
semantics or persistence contracts.

Constraint: Keep the change additive and backward-compatible for existing streaming event consumers
Rejected: Add a separate telemetry subsystem first | broader than the next safe maturity slice
Rejected: Measure only end-to-end request duration | misses the first-token latency signal needed for interactive runtime tuning
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Future streaming observer consumers should treat elapsed_ms as optional metadata and avoid assuming every provider path can produce TTFT
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib map_streaming_callback_data_to_token_event_ -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib handle_turn_with_observer_uses_streaming_request_and_emits_live_events -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib render_cli_chat_live_surface_lines_show_pipeline_status_and_preview -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_emits_phase_and_stream_preview_batches -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_resets_request_scoped_buffers_between_rounds -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No separate benchmark harness or persisted TTFT analytics in this slice
Related: eastreams#1203
Related: eastreams#1194
chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 17, 2026
LoongClaw's runtime host path can now reuse provider HTTP clients, but
that cache was still opaque to runtime snapshots and operator read
models. Without an observable transport-host signal, it remained hard to
confirm whether the cache was warm or whether transport churn had
regressed.

This change adds additive provider transport cache metrics in the app
runtime layer and threads them into daemon runtime snapshot state plus
JSON/text rendering. The cache remains in-process only and request
semantics stay unchanged; this is strictly observability over the
host-owned transport seam.

Constraint: Keep transport metrics additive and observational without changing request/auth semantics
Rejected: Leave the cache opaque and rely on profiling only | makes always-on regressions harder to diagnose in normal operator flows
Rejected: Add a larger telemetry pipeline first | broader than the next safe observability step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If new reqwest-client construction knobs are added, update both ProviderHttpClientCacheKey and the exported runtime metrics so snapshot truth stays aligned with actual cache behavior
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_json_payload_includes_provider_tool_and_external_skill_inventory -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_text_highlights_experiment_relevant_sections -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No cross-process transport cache sharing or benchmark harness in this slice
Related: eastreams#1204
Related: eastreams#1194
@chumyin chumyin force-pushed the feat/runtime-host-foundation-20260410 branch from b709755 to 9611cd4 Compare April 17, 2026 06:17
chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 17, 2026
LoongClaw already records prompt-frame drift and cache-shape truth, but
operators could not see it through the session status surface. That made
prompt progressive-disclosure behavior and token-efficiency debugging too
dependent on raw event logs.

This change adds a best-effort prompt-frame summary block to `sessions
status`, renders a compact operator-facing summary, and extends the
status integration coverage with a seeded prompt-frame snapshot event.
While wiring full verification, I also fixed a gateway owner-state test
isolation issue by making its loaded-config fixtures use per-test runtime
paths instead of a shared `/tmp/loongclaw.toml` baseline, which was
leaking ACP state across tests and invalidating all-features evidence.

Constraint: Keep prompt-frame inspection additive and best-effort so sessions status still works when no prompt-frame events are present
Rejected: Expose raw prompt-frame event payloads verbatim | too noisy for operator inspection
Rejected: Add a larger prompt telemetry subsystem first | broader than the next safe operator-truth slice
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Operator-facing prompt-frame summaries should stay derived from typed event summaries, not ad hoc string parsing of raw assistant history
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw --test integration execute_sessions_command_status_surfaces_workflow_recipes_and_rendered_summary -- --test-threads=1
Tested: cargo test -p loongclaw --test integration gateway_owner_state_localhost_control_surface_requires_auth_and_stops_runtime -- --test-threads=1
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No gateway API/schema expansion for prompt-frame truth in this slice
Related: eastreams#1207
Related: eastreams#1194
chumyin added a commit to chumyin/loongclaw that referenced this pull request Apr 17, 2026
Prompt-frame truth had reached `sessions status`, but the more common
background-task surface still lacked it. That left operator inspection
fragmented for async delegate children and encouraged duplicated prompt
inspection logic in daemon CLI code.

This change extracts the prompt-frame loading/rendering helper into a
shared daemon module, reuses it from sessions and tasks surfaces, and
adds task-status prompt-frame rendering. While landing it, full
verification also reconfirmed the earlier gateway owner-state fixture
isolation fix, so the all-features gate remains trustworthy.

Constraint: Keep prompt-frame inspection additive and derived from typed summaries instead of raw event payload parsing in each surface
Rejected: Duplicate prompt-frame logic inside tasks_cli | would reintroduce slop debt and surface drift
Rejected: Expand gateway/API schemas in the same slice | unnecessary scope for operator CLI parity
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any future operator surface that needs prompt-frame truth should reuse session_prompt_frame_cli helper paths rather than open-coding summary extraction again
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw --test integration execute_sessions_command_status_surfaces_workflow_recipes_and_rendered_summary -- --test-threads=1
Tested: cargo test -p loongclaw --test integration execute_tasks_command_status_surfaces_approval_and_tool_policy -- --test-threads=1
Tested: cargo test -p loongclaw --test integration gateway_owner_state_localhost_control_surface_requires_auth_and_stops_runtime -- --test-threads=1
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No gateway/API prompt-frame parity yet in this slice
Related: eastreams#1208
Related: eastreams#1194
chumyin added 12 commits April 17, 2026 14:18
`loong tasks` used a bespoke DetachedTasksRuntime that reimplemented the
full ConversationRuntime trait only to swap the background-task spawner.
That kept runtime truth fragmented and made future runtime-owned surfaces
more likely to grow their own copy-paste wrappers.

This change introduces a reusable HostedConversationRuntime wrapper in the
conversation runtime layer. It composes over an inner runtime, delegates
all normal runtime behavior, and overrides only the async/background
spawner seams when explicitly requested. Tasks CLI now uses the shared
wrapper instead of owning its own full trait implementation, and a focused
regression test locks the override precedence.

Constraint: Keep this slice refactor-only; do not redesign task execution semantics or introduce a full runtime host yet
Constraint: Preserve existing task create behavior and background owner truth while removing wrapper duplication
Rejected: Keep DetachedTasksRuntime bespoke | preserves drift-prone runtime duplication
Rejected: Build a full persistent AgentRuntimeHost in one patch | too broad for the next trustworthy slice
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: New runtime-owned surfaces should compose through HostedConversationRuntime before adding another bespoke ConversationRuntime wrapper
Tested: cargo fmt --all -- --check
Tested: CARGO_TARGET_DIR=/Users/chum/loongclaw/.cargo-target-shared cargo clippy -p loongclaw-app -p loongclaw --all-targets --all-features -- -D warnings
Tested: CARGO_TARGET_DIR=/Users/chum/loongclaw/.cargo-target-shared cargo test -p loongclaw-app hosted_runtime --lib -- --test-threads=1
Tested: CARGO_TARGET_DIR=/Users/chum/loongclaw/.cargo-target-shared cargo test -p loongclaw --test integration execute_tasks_command_create_queues_background_task_and_surfaces_follow_up_recipes -- --nocapture
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No additional runtime bootstrap caching was added in this slice
Closes: eastreams#1192
Async delegate lifecycle already carried queue/start/timeout truth, but the
operator surfaces still had to infer which runtime host owned a child. That
was especially muddy for background tasks, where a queued/running child could
look the same as a normal async delegate even though it was owned by the
background-task host.

This change adds an additive owner-kind field to constrained subagent
execution metadata, stamps it when async delegates and background tasks are
queued, and projects it into task payloads and text rendering. The existing
lifecycle model stays authoritative; this only makes the owner seam explicit.

Constraint: Keep owner truth additive and reuse the existing delegate lifecycle model rather than inventing a parallel status machine
Constraint: Preserve existing task behavior and session history shape except for the new explicit owner field
Rejected: Infer owner kind ad hoc in tasks_cli from code-path conventions | keeps runtime truth implicit and drift-prone
Rejected: Add pid-level worker tracking in this slice | too broad for the next minimal correct step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep runtime ownership projected through delegate execution metadata so operator surfaces do not guess from call-site conventions
Tested: cargo fmt --all -- --check
Tested: CARGO_TARGET_DIR=/Users/chum/loongclaw/.cargo-target-shared cargo clippy -p loongclaw-app -p loongclaw --all-targets --all-features -- -D warnings
Tested: CARGO_TARGET_DIR=/Users/chum/loongclaw/.cargo-target-shared cargo test -p loongclaw-app constrained_subagent_execution_round_trips_event_payload --lib -- --test-threads=1
Tested: CARGO_TARGET_DIR=/Users/chum/loongclaw/.cargo-target-shared cargo test -p loongclaw-app handle_turn_with_runtime_delegate_async_preserves_kernel_binding_in_spawn_request --lib -- --test-threads=1
Tested: CARGO_TARGET_DIR=/Users/chum/loongclaw/.cargo-target-shared cargo test -p loongclaw --test integration tasks_cli::execute_tasks_command_create_queues_background_task_and_surfaces_follow_up_recipes -- --nocapture
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No pid/heartbeat-level owner telemetry yet; this slice only adds owner-kind truth
Related: eastreams#1192
Closes: eastreams#1195
Default conversation runtime construction was still scattered across
coordinator, turn loop, channel dispatch, delegate support, turn
engine, and daemon task bootstrap sites. That drift weakened the
HostedConversationRuntime seam and made future runtime-host work more
likely to fork behavior.

This change introduces one shared default-runtime loader in the
conversation runtime module, adds a hosted-default loader for runtime
host composition, and routes the remaining app/daemon bootstrap sites
through those helpers. Observer failure signaling remains intact while
background-task runtime composition now reuses the same bootstrap path.

Constraint: Preserve existing context-engine selection and observer failure behavior
Rejected: Introduce a larger runtime factory trait now | unnecessary scope for this convergence slice
Rejected: Add a global cached runtime singleton | premature before lifecycle ownership is explicit
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: New app/daemon default runtime bootstrap sites should use load_default_conversation_runtime or load_hosted_default_conversation_runtime instead of calling from_config_or_env directly
Tested: cargo fmt --all -- --check
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No new benchmark or latency measurement in this refactor-only slice
Related: eastreams#1198
Related: eastreams#1194
Provider request sessions and model-catalog fetches were still building a
fresh reqwest client on every call. That left one of the remaining
always-on/runtime-host seams unnecessarily short-lived even after the
conversation runtime bootstrap convergence work.

This change adds a small host-owned provider HTTP client cache keyed by
the transport policy field that currently affects client construction and
routes both request-session and model-catalog paths through the shared
helper. Auth headers and auth-context lifetimes remain request-scoped, so
transport reuse does not widen credential lifetime.

Constraint: Keep auth headers, auth context, and retry state request-scoped while reusing only transport clients
Rejected: Cache full provider request sessions | would mix transport reuse with auth/model-candidate lifecycle
Rejected: Add benchmark plumbing in the same slice | unnecessary scope for a correctness-first host convergence step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If future provider transport options affect reqwest client construction, extend ProviderHttpClientCacheKey instead of bypassing the shared cache
Tested: cargo fmt --all -- --check
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No dedicated TTFT benchmark yet; this slice proves cache reuse and preserves behavior only
Related: eastreams#1202
Related: eastreams#1194
LoongClaw's streaming turns already emitted token deltas, but the
runtime had no direct first-token signal for operators or live surfaces.
That made TTFT regressions hard to see while runtime-host work was
converging around longer-lived transport and bootstrap seams.

This change adds additive elapsed timing metadata to streaming token
observer events, stamps it from provider-request start in the
coordinator, and threads the first-token latency into the live chat
surface so active turns can show TTFT without changing provider
semantics or persistence contracts.

Constraint: Keep the change additive and backward-compatible for existing streaming event consumers
Rejected: Add a separate telemetry subsystem first | broader than the next safe maturity slice
Rejected: Measure only end-to-end request duration | misses the first-token latency signal needed for interactive runtime tuning
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Future streaming observer consumers should treat elapsed_ms as optional metadata and avoid assuming every provider path can produce TTFT
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib map_streaming_callback_data_to_token_event_ -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib handle_turn_with_observer_uses_streaming_request_and_emits_live_events -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib render_cli_chat_live_surface_lines_show_pipeline_status_and_preview -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_emits_phase_and_stream_preview_batches -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_resets_request_scoped_buffers_between_rounds -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No separate benchmark harness or persisted TTFT analytics in this slice
Related: eastreams#1203
Related: eastreams#1194
LoongClaw's runtime host path can now reuse provider HTTP clients, but
that cache was still opaque to runtime snapshots and operator read
models. Without an observable transport-host signal, it remained hard to
confirm whether the cache was warm or whether transport churn had
regressed.

This change adds additive provider transport cache metrics in the app
runtime layer and threads them into daemon runtime snapshot state plus
JSON/text rendering. The cache remains in-process only and request
semantics stay unchanged; this is strictly observability over the
host-owned transport seam.

Constraint: Keep transport metrics additive and observational without changing request/auth semantics
Rejected: Leave the cache opaque and rely on profiling only | makes always-on regressions harder to diagnose in normal operator flows
Rejected: Add a larger telemetry pipeline first | broader than the next safe observability step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If new reqwest-client construction knobs are added, update both ProviderHttpClientCacheKey and the exported runtime metrics so snapshot truth stays aligned with actual cache behavior
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_json_payload_includes_provider_tool_and_external_skill_inventory -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_text_highlights_experiment_relevant_sections -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No cross-process transport cache sharing or benchmark harness in this slice
Related: eastreams#1204
Related: eastreams#1194
LoongClaw already records prompt-frame drift and cache-shape truth, but
operators could not see it through the session status surface. That made
prompt progressive-disclosure behavior and token-efficiency debugging too
dependent on raw event logs.

This change adds a best-effort prompt-frame summary block to `sessions
status`, renders a compact operator-facing summary, and extends the
status integration coverage with a seeded prompt-frame snapshot event.
While wiring full verification, I also fixed a gateway owner-state test
isolation issue by making its loaded-config fixtures use per-test runtime
paths instead of a shared `/tmp/loongclaw.toml` baseline, which was
leaking ACP state across tests and invalidating all-features evidence.

Constraint: Keep prompt-frame inspection additive and best-effort so sessions status still works when no prompt-frame events are present
Rejected: Expose raw prompt-frame event payloads verbatim | too noisy for operator inspection
Rejected: Add a larger prompt telemetry subsystem first | broader than the next safe operator-truth slice
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Operator-facing prompt-frame summaries should stay derived from typed event summaries, not ad hoc string parsing of raw assistant history
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw --test integration execute_sessions_command_status_surfaces_workflow_recipes_and_rendered_summary -- --test-threads=1
Tested: cargo test -p loongclaw --test integration gateway_owner_state_localhost_control_surface_requires_auth_and_stops_runtime -- --test-threads=1
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No gateway API/schema expansion for prompt-frame truth in this slice
Related: eastreams#1207
Related: eastreams#1194
Prompt-frame truth had reached `sessions status`, but the more common
background-task surface still lacked it. That left operator inspection
fragmented for async delegate children and encouraged duplicated prompt
inspection logic in daemon CLI code.

This change extracts the prompt-frame loading/rendering helper into a
shared daemon module, reuses it from sessions and tasks surfaces, and
adds task-status prompt-frame rendering. While landing it, full
verification also reconfirmed the earlier gateway owner-state fixture
isolation fix, so the all-features gate remains trustworthy.

Constraint: Keep prompt-frame inspection additive and derived from typed summaries instead of raw event payload parsing in each surface
Rejected: Duplicate prompt-frame logic inside tasks_cli | would reintroduce slop debt and surface drift
Rejected: Expand gateway/API schemas in the same slice | unnecessary scope for operator CLI parity
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any future operator surface that needs prompt-frame truth should reuse session_prompt_frame_cli helper paths rather than open-coding summary extraction again
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw --test integration execute_sessions_command_status_surfaces_workflow_recipes_and_rendered_summary -- --test-threads=1
Tested: cargo test -p loongclaw --test integration execute_tasks_command_status_surfaces_approval_and_tool_policy -- --test-threads=1
Tested: cargo test -p loongclaw --test integration gateway_owner_state_localhost_control_surface_requires_auth_and_stops_runtime -- --test-threads=1
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No gateway/API prompt-frame parity yet in this slice
Related: eastreams#1208
Related: eastreams#1194
Add shared daemon-side loading and rendering for safe-lane and turn-checkpoint summaries, then wire those summaries into session and task inspection surfaces alongside the existing prompt-frame truth. This keeps operator status flows anchored to the existing conversation summary loaders instead of introducing a separate read model.

This slice also aligns task CLI terminal rendering with the session CLI sanitization pattern so runtime-truth lines, task labels, lookup errors, and event kinds do not leak raw control characters. Regression coverage now locks both the new runtime-truth payload/render paths and the terminal-sanitization behavior.

Constraint: Keep the change additive to existing session/task payload shapes and text shells
Rejected: Build a new daemon-wide runtime read model first | existing summary loaders already expose the required truth with a smaller diff
Rejected: Add only safe-lane or only turn-checkpoint status lines | operators need loop-health and durability context together
Confidence: high
Scope-risk: narrow
Directive: Keep operator-facing runtime truth additive and sourced from the existing conversation summary loaders unless a single shared runtime host/read-model seam is introduced deliberately
Tested: cargo fmt --all -- --check; cargo clippy --workspace --all-targets --all-features -- -D warnings; cargo test --workspace --locked -- --test-threads=1; cargo test --workspace --all-features --locked -- --test-threads=1; ./scripts/check_architecture_boundaries.sh; ./scripts/check_dep_graph.sh; cargo test -p loongclaw --test integration execute_sessions_command_status_surfaces_workflow_recipes_and_rendered_summary -- --test-threads=1; cargo test -p loongclaw --test integration execute_tasks_command_status_surfaces_approval_and_tool_policy -- --test-threads=1; cargo test -p loongclaw --test integration render_tasks_status_text_escapes_control_characters -- --test-threads=1; cargo test -p loongclaw --test integration render_tasks_events_text_escapes_control_characters -- --test-threads=1
Not-tested: Best-effort degradation when secondary task inspection queries fail independently of core session status
Task operator inspection already had enough status truth to remain useful when approval-list or tool-policy lookups fail, but the read path still treated those secondary queries as hard dependencies. This change keeps session/task status as the fail-closed gate while degrading approval and tool-policy lookups into additive error fields that preserve the rest of the payload.

The slice also stabilizes the fallback task payload shape used by create/cancel/recover hydration failures so downstream readers do not see a narrower schema during degraded paths. Focused tests now lock the local degradation helpers, the composed detail payload, the fallback schema, and the rendered warning lines.

Constraint: Preserve existing task detail fields and text shells while making the degradation additive
Rejected: Introduce a new daemon-wide read model for task inspection | too large for the bounded operator-surface fix
Rejected: Make all task inspection dependencies best-effort | core task/session status still needs to fail closed for invisible or non-background targets
Confidence: high
Scope-risk: narrow
Directive: Keep secondary operator lookups additive and degrade them into explicit payload warnings instead of replacing the whole task detail with a narrower fallback shape
Tested: cargo fmt --all -- --check; cargo clippy --workspace --all-targets --all-features -- -D warnings; cargo test --workspace --locked -- --test-threads=1; cargo test --workspace --all-features --locked -- --test-threads=1; ./scripts/check_architecture_boundaries.sh; ./scripts/check_dep_graph.sh; cargo test -p loongclaw --lib best_effort_task_ -- --test-threads=1; cargo test -p loongclaw --lib compose_task_detail_payload_keeps_core_status_truth_when_secondary_lookups_degrade -- --test-threads=1; cargo test -p loongclaw --test integration execute_tasks_command_create_returns_queued_outcome_when_task_hydration_fails -- --test-threads=1; cargo test -p loongclaw --test integration render_tasks_status_text_escapes_control_characters -- --test-threads=1; cargo test -p loongclaw --test integration execute_tasks_command_status_surfaces_approval_and_tool_policy -- --test-threads=1
Not-tested: End-to-end execution where session_status succeeds while approval or tool-policy lookup independently fails for a live repository-backed task record
@chumyin chumyin force-pushed the feat/runtime-host-foundation-20260410 branch from 9611cd4 to 219119c Compare April 17, 2026 06:19
@chumyin chumyin merged commit 5b7f318 into eastreams:dev Apr 17, 2026
18 checks passed
chumyin added a commit that referenced this pull request Apr 17, 2026
Default conversation runtime construction was still scattered across
coordinator, turn loop, channel dispatch, delegate support, turn
engine, and daemon task bootstrap sites. That drift weakened the
HostedConversationRuntime seam and made future runtime-host work more
likely to fork behavior.

This change introduces one shared default-runtime loader in the
conversation runtime module, adds a hosted-default loader for runtime
host composition, and routes the remaining app/daemon bootstrap sites
through those helpers. Observer failure signaling remains intact while
background-task runtime composition now reuses the same bootstrap path.

Constraint: Preserve existing context-engine selection and observer failure behavior
Rejected: Introduce a larger runtime factory trait now | unnecessary scope for this convergence slice
Rejected: Add a global cached runtime singleton | premature before lifecycle ownership is explicit
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: New app/daemon default runtime bootstrap sites should use load_default_conversation_runtime or load_hosted_default_conversation_runtime instead of calling from_config_or_env directly
Tested: cargo fmt --all -- --check
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No new benchmark or latency measurement in this refactor-only slice
Related: #1198
Related: #1194
chumyin added a commit that referenced this pull request Apr 17, 2026
Provider request sessions and model-catalog fetches were still building a
fresh reqwest client on every call. That left one of the remaining
always-on/runtime-host seams unnecessarily short-lived even after the
conversation runtime bootstrap convergence work.

This change adds a small host-owned provider HTTP client cache keyed by
the transport policy field that currently affects client construction and
routes both request-session and model-catalog paths through the shared
helper. Auth headers and auth-context lifetimes remain request-scoped, so
transport reuse does not widen credential lifetime.

Constraint: Keep auth headers, auth context, and retry state request-scoped while reusing only transport clients
Rejected: Cache full provider request sessions | would mix transport reuse with auth/model-candidate lifecycle
Rejected: Add benchmark plumbing in the same slice | unnecessary scope for a correctness-first host convergence step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If future provider transport options affect reqwest client construction, extend ProviderHttpClientCacheKey instead of bypassing the shared cache
Tested: cargo fmt --all -- --check
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No dedicated TTFT benchmark yet; this slice proves cache reuse and preserves behavior only
Related: #1202
Related: #1194
chumyin added a commit that referenced this pull request Apr 17, 2026
LoongClaw's streaming turns already emitted token deltas, but the
runtime had no direct first-token signal for operators or live surfaces.
That made TTFT regressions hard to see while runtime-host work was
converging around longer-lived transport and bootstrap seams.

This change adds additive elapsed timing metadata to streaming token
observer events, stamps it from provider-request start in the
coordinator, and threads the first-token latency into the live chat
surface so active turns can show TTFT without changing provider
semantics or persistence contracts.

Constraint: Keep the change additive and backward-compatible for existing streaming event consumers
Rejected: Add a separate telemetry subsystem first | broader than the next safe maturity slice
Rejected: Measure only end-to-end request duration | misses the first-token latency signal needed for interactive runtime tuning
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Future streaming observer consumers should treat elapsed_ms as optional metadata and avoid assuming every provider path can produce TTFT
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib map_streaming_callback_data_to_token_event_ -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib handle_turn_with_observer_uses_streaming_request_and_emits_live_events -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib render_cli_chat_live_surface_lines_show_pipeline_status_and_preview -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_emits_phase_and_stream_preview_batches -- --test-threads=1
Tested: cargo test -p loongclaw-app --lib cli_chat_live_surface_observer_resets_request_scoped_buffers_between_rounds -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No separate benchmark harness or persisted TTFT analytics in this slice
Related: #1203
Related: #1194
chumyin added a commit that referenced this pull request Apr 17, 2026
LoongClaw's runtime host path can now reuse provider HTTP clients, but
that cache was still opaque to runtime snapshots and operator read
models. Without an observable transport-host signal, it remained hard to
confirm whether the cache was warm or whether transport churn had
regressed.

This change adds additive provider transport cache metrics in the app
runtime layer and threads them into daemon runtime snapshot state plus
JSON/text rendering. The cache remains in-process only and request
semantics stay unchanged; this is strictly observability over the
host-owned transport seam.

Constraint: Keep transport metrics additive and observational without changing request/auth semantics
Rejected: Leave the cache opaque and rely on profiling only | makes always-on regressions harder to diagnose in normal operator flows
Rejected: Add a larger telemetry pipeline first | broader than the next safe observability step
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If new reqwest-client construction knobs are added, update both ProviderHttpClientCacheKey and the exported runtime metrics so snapshot truth stays aligned with actual cache behavior
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw-app --lib provider_http_client_cache_ -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_json_payload_includes_provider_tool_and_external_skill_inventory -- --test-threads=1
Tested: cargo test -p loongclaw --test integration runtime_snapshot_text_highlights_experiment_relevant_sections -- --test-threads=1
Tested: cargo clippy -p loongclaw-app --all-targets --all-features -- -D warnings
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No cross-process transport cache sharing or benchmark harness in this slice
Related: #1204
Related: #1194
chumyin added a commit that referenced this pull request Apr 17, 2026
LoongClaw already records prompt-frame drift and cache-shape truth, but
operators could not see it through the session status surface. That made
prompt progressive-disclosure behavior and token-efficiency debugging too
dependent on raw event logs.

This change adds a best-effort prompt-frame summary block to `sessions
status`, renders a compact operator-facing summary, and extends the
status integration coverage with a seeded prompt-frame snapshot event.
While wiring full verification, I also fixed a gateway owner-state test
isolation issue by making its loaded-config fixtures use per-test runtime
paths instead of a shared `/tmp/loongclaw.toml` baseline, which was
leaking ACP state across tests and invalidating all-features evidence.

Constraint: Keep prompt-frame inspection additive and best-effort so sessions status still works when no prompt-frame events are present
Rejected: Expose raw prompt-frame event payloads verbatim | too noisy for operator inspection
Rejected: Add a larger prompt telemetry subsystem first | broader than the next safe operator-truth slice
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Operator-facing prompt-frame summaries should stay derived from typed event summaries, not ad hoc string parsing of raw assistant history
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw --test integration execute_sessions_command_status_surfaces_workflow_recipes_and_rendered_summary -- --test-threads=1
Tested: cargo test -p loongclaw --test integration gateway_owner_state_localhost_control_surface_requires_auth_and_stops_runtime -- --test-threads=1
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No gateway API/schema expansion for prompt-frame truth in this slice
Related: #1207
Related: #1194
chumyin added a commit that referenced this pull request Apr 17, 2026
Prompt-frame truth had reached `sessions status`, but the more common
background-task surface still lacked it. That left operator inspection
fragmented for async delegate children and encouraged duplicated prompt
inspection logic in daemon CLI code.

This change extracts the prompt-frame loading/rendering helper into a
shared daemon module, reuses it from sessions and tasks surfaces, and
adds task-status prompt-frame rendering. While landing it, full
verification also reconfirmed the earlier gateway owner-state fixture
isolation fix, so the all-features gate remains trustworthy.

Constraint: Keep prompt-frame inspection additive and derived from typed summaries instead of raw event payload parsing in each surface
Rejected: Duplicate prompt-frame logic inside tasks_cli | would reintroduce slop debt and surface drift
Rejected: Expand gateway/API schemas in the same slice | unnecessary scope for operator CLI parity
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any future operator surface that needs prompt-frame truth should reuse session_prompt_frame_cli helper paths rather than open-coding summary extraction again
Tested: cargo fmt --all -- --check
Tested: cargo test -p loongclaw --test integration execute_sessions_command_status_surfaces_workflow_recipes_and_rendered_summary -- --test-threads=1
Tested: cargo test -p loongclaw --test integration execute_tasks_command_status_surfaces_approval_and_tool_policy -- --test-threads=1
Tested: cargo test -p loongclaw --test integration gateway_owner_state_localhost_control_surface_requires_auth_and_stops_runtime -- --test-threads=1
Tested: cargo clippy -p loongclaw --all-targets --all-features -- -D warnings
Tested: cargo clippy --workspace --all-targets --all-features -- -D warnings
Tested: cargo test --workspace --locked -- --test-threads=1
Tested: cargo test --workspace --all-features --locked -- --test-threads=1
Tested: ./scripts/check_architecture_boundaries.sh
Tested: ./scripts/check_dep_graph.sh
Not-tested: No gateway/API prompt-frame parity yet in this slice
Related: #1208
Related: #1194
@chumyin chumyin deleted the feat/runtime-host-foundation-20260410 branch April 17, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

acp ACP manager, binding, routing, and control plane surfaces. conversation Conversation runtime, session flow, and prompt assembly. daemon Daemon binary, CLI entrypoints, and install flow. documentation Improvements or additions to documentation. providers Provider routing, selection, and transport behavior. size: XL Very large pull request: more than 1000 changed lines. tools Tool runtime, policy adapters, and tool catalog behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Introduce a reusable hosted conversation runtime wrapper

1 participant