Skip to content

perf(coding-agent): concurrent skill loading + startup indicator through TUI import - #1282

Open
1Morganmore wants to merge 3 commits into
code-yeongyu:mainfrom
1Morganmore:perf/omostartup-engine
Open

perf(coding-agent): concurrent skill loading + startup indicator through TUI import#1282
1Morganmore wants to merge 3 commits into
code-yeongyu:mainfrom
1Morganmore:perf/omostartup-engine

Conversation

@1Morganmore

@1Morganmore 1Morganmore commented Sep 2, 2026

Copy link
Copy Markdown

What

Two startup fixes in one branch:

  1. Concurrent skill loadingloadSkillsFromDirInternal read every SKILL.md sequentially with readFileSync, serializing disk access at startup (a user with ~110 skills paid ~1.2s cold on this Windows host). It now collects the file work in traversal order and runs the IO-bound reads with Promise.all, preserving skill/diagnostic order exactly. loadSkills/loadSkillsFromDir become async; extendResources, updateSkillsFromPaths, the app-server skills loader, and the agent-session call site await them.
  2. Startup indicator through the TUI import — the loading indicator stopped right after createAgentSessionRuntime, before the dynamic import of the interactive-mode module graph (the largest cold-start cost after resource loading), leaving a blank terminal that looked frozen. It now stops only after the import.

QA

  • Vitest: test/skills.test.ts (existing 27-case suite adapted to the async API + 1 new canonical-path dedupe case) and test/imagegen-skill-gating.test.ts31/31 green (Vitest runs both files).
  • packages/coding-agent/changes.md updated with both entries.
  • (Note: root bun run check/tsgo full static gates not run locally — this machine cannot build the sibling workspace packages; CI runs them.)

Why it is enough

The refactor changes only WHEN reads happen (parallel, awaited before publish) and keeps the returned order; the adapted original suite is the behavior lock.

STOP WHEN

CI green and one approval.


Summary by cubic

Speeds up startup by loading skill files concurrently and keeps the startup indicator spinning through the TUI import so the terminal no longer appears frozen.

  • loadSkills and loadSkillsFromDir are now async; all call sites await them, and reload() awaits skill loading so the loaded state is published before it completes.
  • Skill file reads run in parallel while preserving skill and diagnostic order.
  • The startup indicator stops after the interactive-mode import, inside a finally, so an import failure can't leave the spinner running with the cursor hidden.
  • Existing skill tests were adapted to the async API, with a new canonical-path dedupe case added.

Written for commit 7eb6b8e. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/coding-agent/src/core/resource-loader.ts">

<violation number="1" location="packages/coding-agent/src/core/resource-loader.ts:523">
P2: When callers invoke `extendResources()` without awaiting it, newly discovered skills are not available on the next statement. Update every caller and the existing resource-loader tests to await the new Promise-returning API before reading resources.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/coding-agent/src/core/resource-loader.ts
Comment thread packages/coding-agent/src/main.ts Outdated
}

extendResources(paths: ResourceExtensionPaths): void {
async extendResources(paths: ResourceExtensionPaths): Promise<void> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When callers invoke extendResources() without awaiting it, newly discovered skills are not available on the next statement. Update every caller and the existing resource-loader tests to await the new Promise-returning API before reading resources.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/coding-agent/src/core/resource-loader.ts, line 523:

<comment>When callers invoke `extendResources()` without awaiting it, newly discovered skills are not available on the next statement. Update every caller and the existing resource-loader tests to await the new Promise-returning API before reading resources.</comment>

<file context>
@@ -520,7 +520,7 @@ export class DefaultResourceLoader implements ResourceLoader {
 	}
 
-	extendResources(paths: ResourceExtensionPaths): void {
+	async extendResources(paths: ResourceExtensionPaths): Promise<void> {
 		const skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);
 		const promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);
</file context>

Comment thread packages/coding-agent/test/skills.test.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/coding-agent/test/skills.test.ts">

<violation number="1" location="packages/coding-agent/test/skills.test.ts:439">
P3: The new try/finally only wraps the assertions, so if the async loadSkills() call above it rejects (e.g. a read failure), the mkdtempSync() temp directory is never cleaned up. Move the loadSkills() call inside the try so the created dir is always removed.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

try {
expect(skills).toHaveLength(1);
expect(skills[0]?.name).toBe("dup-skill");
} finally {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new try/finally only wraps the assertions, so if the async loadSkills() call above it rejects (e.g. a read failure), the mkdtempSync() temp directory is never cleaned up. Move the loadSkills() call inside the try so the created dir is always removed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/coding-agent/test/skills.test.ts, line 439:

<comment>The new try/finally only wraps the assertions, so if the async loadSkills() call above it rejects (e.g. a read failure), the mkdtempSync() temp directory is never cleaned up. Move the loadSkills() call inside the try so the created dir is always removed.</comment>

<file context>
@@ -433,7 +433,11 @@ describe("loadSkills canonical path dedupe", () => {
+		try {
+			expect(skills).toHaveLength(1);
+			expect(skills[0]?.name).toBe("dup-skill");
+		} finally {
+			rmSync(root, { recursive: true, force: true });
+		}
</file context>

KDH added 3 commits September 2, 2026 18:08
loadSkillsFromDirInternal read every SKILL.md sequentially with readFileSync;
with many skills (and slow disks) that serializes disk access during startup.
Collect the file work in traversal order, then run the IO-bound reads with
Promise.all while preserving skill/diagnostic order exactly. loadSkills and
loadSkillsFromDir become async; extendResources, updateSkillsFromPaths and
the app-server skills loader await them. Existing skills suite adapted to
the async API (27 cases) plus one new canonical-path dedupe case; the
imagegen skill-gating test awaits loadSkills.
…port

The startup loading indicator stopped right after createAgentSessionRuntime,
before the dynamic import of the interactive-mode module graph — the single
largest cold-start cost after resource loading — leaving a blank terminal
that looked frozen. Stop it only after the import (still before the TUI
starts writing stdout), so the spinner covers the wait.
- reload(): await updateSkillsFromPaths so the loaded state is published
  before reload() completes (skills were assigned asynchronously after the
  caller observed the loader).
- main.ts: stop the startup indicator in a finally around the
  interactive-mode import so an import rejection cannot leave the spinner
  running with the cursor hidden.
- skills.test.ts: clean up the dedupe fixture temp dir.
- resource-loader.test.ts / hooks-builtin-extension.test.ts: await the new
  Promise-returning extendResources API.
@1Morganmore
1Morganmore force-pushed the perf/omostartup-engine branch from d83ea2e to 7eb6b8e Compare September 2, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant