Skip to content

feat(agent-runtime): add getLatestRunId to resolve a thread's latest run#451

Merged
jerryliang64 merged 1 commit into
masterfrom
feat-recent-run-id
Jun 10, 2026
Merged

feat(agent-runtime): add getLatestRunId to resolve a thread's latest run#451
jerryliang64 merged 1 commit into
masterfrom
feat-recent-run-id

Conversation

@jerryliang64

@jerryliang64 jerryliang64 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

What

Adds a platform-level capability to resolve a thread's most latest run without an external index, so @AgentController consumers no longer need to persist runId/seq on their side.

New endpoint: GET /api/v1/threads/:id/latest-run{ threadId, runId }.

How

  • AgentStore / ThreadRecord: new optional latestRunId on thread meta, and a getLatestRunId(threadId): Promise<string | null> store method.
  • OSSAgentStore: createRun records latestRunId into the thread meta.json via a best-effort read-modify-write (swallowed + warned on failure, so it never blocks run creation). getLatestRunId reads it back.
  • AgentRuntime: getLatestRunId(threadId) → { threadId, runId }.
  • @AgentController: registers the new route, wired through the existing not-implemented-stub / runtime-delegate mechanism (AGENT_METHOD_NAMES).

Semantics

  • thread missing → AgentNotFoundError (404), consistent with getThread/getRun
  • thread exists, no run yet (incl. threads created before this field existed) → runId: null
  • otherwise → the latest run's id

Backward compatibility

latestRunId is optional; old thread meta.json parses fine and degrades to a null latest-run. No migration needed.

Consistency note

latestRunId is a best-effort, weakly-consistent pointer: runs on a thread are normally sequential, but concurrent createRun on the same thread is last-write-wins, so it may momentarily point at a non-latest run. Only latestRunId can race — all other thread metadata is preserved.

Tests

  • OSSAgentStore: record/read, multiple-run latest, metadata preservation, historical null, no-threadId, missing-thread best-effort, not-found throw.
  • AgentRuntime: delegation + return shape + null + not-found.
  • @AgentController: route table updated (count + route metadata).

All affected packages pass tsc; agent-runtime / controller-decorator / plugin-controller test suites green.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Expose a method to retrieve a thread’s most recent run ID; returns null if the thread exists but has no runs.
    • Best-effort background update records the latest run on thread metadata without failing run creation.
    • New HTTP endpoint: GET /threads/:id/latest-run.
  • Tests

    • Added tests covering latest-run behavior, pointer updates, metadata preservation, null-for-no-runs, and missing-thread errors.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

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 per-thread recent-run-id recording and retrieval: types and handler hook, OSSAgentStore best-effort metadata writes and lookup, AgentRuntime delegation returning { threadId, runId }, HTTP route registration, and tests covering success and edge cases.

Changes

Recent Run ID Tracking Feature

Layer / File(s) Summary
Type contracts for recent run ID lookup
core/types/agent-runtime/AgentStore.ts, core/controller-decorator/src/decorator/agent/AgentHandler.ts
ThreadRecord gains optional lastRunId field, AgentStore interface declares getRecentRunId(threadId) method returning string | null and throwing AgentNotFoundError for missing threads, and AgentHandler hook adds optional getRecentRunId capability.
Store implementation and metadata persistence
core/agent-runtime/src/OSSAgentStore.ts
OSSAgentStore.createRun conditionally schedules a best-effort recordLastRunId after run creation (tracked via pendingIndexWrites); recordLastRunId performs read-modify-write on thread meta.json, swallowing and logging missing metadata or storage/parse/write errors; getRecentRunId reads meta.json and returns meta.lastRunId or null, throwing AgentNotFoundError for absent thread metadata.
OSSAgentStore tests
core/agent-runtime/test/OSSAgentStore.test.ts
Tests validate pointer recording when threadId is provided, most-recent updates across multiple runs, preservation of existing metadata, null for threads without runs, omission when createRun has no threadId, best-effort behavior for missing thread targets, and 404 semantics when querying non-existent threads; tests drain pending writes via awaitPendingWrites().
Runtime delegation and query response
core/agent-runtime/src/AgentRuntime.ts, core/agent-runtime/test/AgentRuntime.test.ts
AgentRuntime.getRecentRunId delegates to store and returns { threadId, runId | null }; tests ensure retrieval after runs, null for no runs, and rejection for non-existent threads.
HTTP routing and controller integration
core/controller-decorator/src/decorator/agent/AgentController.ts, plugin/controller/lib/AgentControllerObject.ts, core/controller-decorator/test/AgentController.test.ts
AgentController registers GET /threads/:id/recent-run; AgentControllerObject adds getLatestRunId to delegatable method names; controller tests updated to include route, parameter metadata, stub behavior, and integration expectations.

Sequence diagram

sequenceDiagram
  participant Client
  participant AgentController
  participant AgentRuntime
  participant OSSAgentStore
  participant Storage
  Client->>AgentController: GET /threads/:id/latest-run
  AgentController->>AgentRuntime: getLatestRunId(threadId)
  AgentRuntime->>OSSAgentStore: getLatestRunId(threadId)
  OSSAgentStore->>Storage: read threads/{id}/meta.json
  Storage-->>OSSAgentStore: meta.json (may contain latestRunId)
  OSSAgentStore-->>AgentRuntime: runId|null
  AgentRuntime-->>AgentController: {threadId, runId}
  AgentController-->>Client: HTTP 200 / 404
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • eggjs/tegg#446: Both PRs touch thread meta.json contents; #446 adds thread metadata persistence that this PR updates with latestRunId.
  • eggjs/tegg#445: Introduced pendingIndexWrites/background-write drain patterns used by this PR to track best-effort meta writes.

Suggested reviewers

  • akitaSummer

Poem

🐰 I hop through threads both near and far,
Hunting the last run ID like a star.
Meta softly updated, errors let go,
Runtime and route now rightly know.
Hooray — the recent run is on show!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a new getLatestRunId method to the agent-runtime to resolve a thread's latest run.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-recent-run-id

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

core/agent-runtime/src/AgentRuntime.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

core/agent-runtime/src/OSSAgentStore.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

core/agent-runtime/test/AgentRuntime.test.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 6 others

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a mechanism to track and retrieve the most recent run ID associated with a thread by adding a lastRunId field to thread metadata and exposing a new getRecentRunId endpoint. Feedback suggests making the recordLastRunId write non-blocking to prevent adding network latency to the critical path of run creation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +221 to +223
if (threadId) {
await this.recordLastRunId(threadId, runId);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Awaiting recordLastRunId synchronously blocks the run creation process, adding two extra network roundtrips (one GET and one PUT to the object storage) to the critical path of createRun. Since the client already receives the runId in the response of the run creation call (or via the run_created event in streaming), they do not need to immediately query getRecentRunId to find it.\n\nWe can make this write non-blocking (running in the background) and track it using the existing pendingIndexWrites set so that destroy() still safely awaits it before shutdown. This significantly improves the latency of starting a run.

    if (threadId) {\n      const p = this.recordLastRunId(threadId, runId)\n        .finally(() => {\n          this.pendingIndexWrites.delete(p);\n        });\n      this.pendingIndexWrites.add(p);\n    }

@coderabbitai coderabbitai 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.

Caution

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

⚠️ Outside diff range comments (3)
core/controller-decorator/test/AgentController.test.ts (3)

108-116: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing getRecentRunId in context index test.

The methods array doesn't include getRecentRunId, so the new method isn't being tested to verify it has no contextIndex set.

📋 Proposed fix to include getRecentRunId in context index test
     it('should not set contextIndex on any method', () => {
-      const methods = [ 'createThread', 'getThread', 'asyncRun', 'streamRun', 'syncRun', 'getRun', 'cancelRun' ];
+      const methods = [ 'createThread', 'getThread', 'getRecentRunId', 'asyncRun', 'streamRun', 'syncRun', 'getRun', 'cancelRun' ];
       for (const methodName of methods) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/controller-decorator/test/AgentController.test.ts` around lines 108 -
116, Add the missing method name to the test: update the methods array in the
'context index' test to include "getRecentRunId" so
MethodInfoUtil.getMethodContextIndex(AgentFooController, 'getRecentRunId') is
also asserted to be undefined; this ensures AgentFooController's getRecentRunId
is checked alongside createThread, getThread, asyncRun, streamRun, syncRun,
getRun, and cancelRun.

147-163: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing test case for getRecentRunId stub error behavior.

The stubMethods array doesn't include getRecentRunId, so there's no test verifying that the default stub throws the expected error message when called.

📋 Proposed fix to test getRecentRunId stub behavior
     const stubMethods = [
       { name: 'createThread', args: [] },
       { name: 'getThread', args: [ 'thread_1' ] },
+      { name: 'getRecentRunId', args: [ 'thread_1' ] },
       { name: 'asyncRun', args: [{ input: { messages: [] } }] },
       { name: 'streamRun', args: [{ input: { messages: [] } }] },
       { name: 'getRunStream', args: [ 'run_1', '0' ] },
       { name: 'syncRun', args: [{ input: { messages: [] } }] },
       { name: 'getRun', args: [ 'run_1' ] },
       { name: 'cancelRun', args: [ 'run_1' ] },
     ];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/controller-decorator/test/AgentController.test.ts` around lines 147 -
163, Add a test for the missing stub method getRecentRunId: update the
stubMethods array in AgentController.test.ts to include { name:
'getRecentRunId', args: [ 'thread_1' ] } (or appropriate args) and ensure the
for-loop creates an it(...) case asserting that calling
instance.getRecentRunId(...args) rejects with /getRecentRunId not implemented/;
reference the AgentFooController instantiation and the existing test pattern
used for other stub methods so the new test mirrors those assertions.

132-145: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Incomplete test coverage for getRecentRunId default stub.

The test description says "all 8 route methods" but should now be 9. More importantly, the routeMethods array on line 136 doesn't include 'getRecentRunId', so the new method is not being tested to verify it has a default stub marked as AGENT_NOT_IMPLEMENTED.

📋 Proposed fix to add getRecentRunId to test coverage
-    it('should inject default stubs for all 8 route methods', () => {
+    it('should inject default stubs for all 9 route methods', () => {
       // AgentFooController only implements execRun (smart defaults pattern)
-      // All 8 route methods should have stub defaults that throw
+      // All 9 route methods should have stub defaults that throw
       const proto = AgentFooController.prototype as any;
-      const routeMethods = [ 'createThread', 'getThread', 'asyncRun', 'streamRun', 'getRunStream', 'syncRun', 'getRun', 'cancelRun' ];
+      const routeMethods = [ 'createThread', 'getThread', 'getRecentRunId', 'asyncRun', 'streamRun', 'getRunStream', 'syncRun', 'getRun', 'cancelRun' ];
       for (const methodName of routeMethods) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/controller-decorator/test/AgentController.test.ts` around lines 132 -
145, The test in AgentController.test.ts asserts that the controller prototype
has default stubs for all route methods but the routeMethods array (used in the
it block) omits 'getRecentRunId'; update the routeMethods array inside the test
that iterates over AgentFooController.prototype to include 'getRecentRunId' so
the test checks that proto['getRecentRunId'] is a function and that
AgentInfoUtil.isNotImplemented(proto['getRecentRunId']) is true, matching the
other route method assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@core/controller-decorator/test/AgentController.test.ts`:
- Around line 108-116: Add the missing method name to the test: update the
methods array in the 'context index' test to include "getRecentRunId" so
MethodInfoUtil.getMethodContextIndex(AgentFooController, 'getRecentRunId') is
also asserted to be undefined; this ensures AgentFooController's getRecentRunId
is checked alongside createThread, getThread, asyncRun, streamRun, syncRun,
getRun, and cancelRun.
- Around line 147-163: Add a test for the missing stub method getRecentRunId:
update the stubMethods array in AgentController.test.ts to include { name:
'getRecentRunId', args: [ 'thread_1' ] } (or appropriate args) and ensure the
for-loop creates an it(...) case asserting that calling
instance.getRecentRunId(...args) rejects with /getRecentRunId not implemented/;
reference the AgentFooController instantiation and the existing test pattern
used for other stub methods so the new test mirrors those assertions.
- Around line 132-145: The test in AgentController.test.ts asserts that the
controller prototype has default stubs for all route methods but the
routeMethods array (used in the it block) omits 'getRecentRunId'; update the
routeMethods array inside the test that iterates over
AgentFooController.prototype to include 'getRecentRunId' so the test checks that
proto['getRecentRunId'] is a function and that
AgentInfoUtil.isNotImplemented(proto['getRecentRunId']) is true, matching the
other route method assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 641f13fa-997e-445f-bef5-92333d388b14

📥 Commits

Reviewing files that changed from the base of the PR and between 5d90357 and 7cf8005.

📒 Files selected for processing (9)
  • core/agent-runtime/src/AgentRuntime.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/test/AgentRuntime.test.ts
  • core/agent-runtime/test/OSSAgentStore.test.ts
  • core/controller-decorator/src/decorator/agent/AgentController.ts
  • core/controller-decorator/src/decorator/agent/AgentHandler.ts
  • core/controller-decorator/test/AgentController.test.ts
  • core/types/agent-runtime/AgentStore.ts
  • plugin/controller/lib/AgentControllerObject.ts

@jerryliang64 jerryliang64 force-pushed the feat-recent-run-id branch 2 times, most recently from 14d5607 to 27a4223 Compare June 8, 2026 07:17
@jerryliang64

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Adopted the suggestion: recordLastRunId is now scheduled fire-and-forget and tracked in the existing pendingIndexWrites set (mirroring writeThreadActivityIndex), so it no longer adds a GET+PUT roundtrip to the createRun hot path while destroy() still drains it before shutdown. getRecentRunId is therefore eventually consistent, which matches its usage (the run creator already has the runId from the response/run_created event; getRecentRunId is for a later/separate lookup). Tests updated to awaitPendingWrites() before asserting. Docs updated accordingly.

@jerryliang64

Copy link
Copy Markdown
Contributor Author

@coderabbitai Adopted all 3 test-coverage findings in AgentController.test.ts: added getRecentRunId to the context index, default stubs (count 8→9), and unimplemented-stub-throws enumerations so the new route is asserted alongside the existing ones. controller-decorator suite now 78 passing.

Note: the Gemini comment on OSSAgentStore.ts (make recordLastRunId non-blocking) was already addressed in the previous push — createRun now schedules it fire-and-forget via pendingIndexWrites; the re-post is on the updated commit but the suggested change is already in place.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@jerryliang64 Thanks for the thorough update! All three AgentController.test.ts gaps are now covered — the getRecentRunId entry in the context-index, default-stubs (8 → 9), and unimplemented-stub-throws assertions, all confirmed by the 78-passing suite. And good to know the recordLastRunId fire-and-forget change pre-dated the re-posted Gemini comment — no action needed there.

🐇 ✨

Adds a platform-level capability to look up the most recent run of a
thread without an external index, so consumers no longer need to persist
runId/seq on their side.

- AgentStore/ThreadRecord: new optional `latestRunId` on thread meta and a
  `getLatestRunId(threadId)` store method.
- OSSAgentStore: `createRun` records `latestRunId` into the thread meta.json
  (best-effort read-modify-write; never blocks run creation), and
  `getLatestRunId` reads it back — null when the thread has no run yet
  (incl. threads created before this field existed), AgentNotFoundError
  when the thread itself is missing.
- AgentRuntime: `getLatestRunId(threadId) -> { threadId, runId }`.
- @AgentController: new route GET /api/v1/threads/:id/latest-run, wired
  through the existing stub/delegate mechanism.

Backward compatible: `latestRunId` is optional, old thread meta parses fine
and degrades to a null latest-run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jerryliang64 jerryliang64 changed the title feat(agent-runtime): add getRecentRunId to resolve a thread's latest run feat(agent-runtime): add getLatestRunId to resolve a thread's latest run Jun 10, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/agent-runtime/test/OSSAgentStore.test.ts`:
- Around line 264-268: The test "should not record latestRunId when createRun
has no threadId" currently only checks run.id; update it to create a thread
first (use store.createThread), then call store.createRun without a threadId,
keep the existing assertion that run.id startsWith('run_'), then retrieve the
original thread (e.g., store.getThread or store.getThreadById) and assert that
the thread's latestRunId (thread.latestRunId) is still null/undefined to ensure
no pointer was recorded.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25207054-1d35-4c09-af34-8d42febf4f49

📥 Commits

Reviewing files that changed from the base of the PR and between 03e9449 and 70a99da.

📒 Files selected for processing (9)
  • core/agent-runtime/src/AgentRuntime.ts
  • core/agent-runtime/src/OSSAgentStore.ts
  • core/agent-runtime/test/AgentRuntime.test.ts
  • core/agent-runtime/test/OSSAgentStore.test.ts
  • core/controller-decorator/src/decorator/agent/AgentController.ts
  • core/controller-decorator/src/decorator/agent/AgentHandler.ts
  • core/controller-decorator/test/AgentController.test.ts
  • core/types/agent-runtime/AgentStore.ts
  • plugin/controller/lib/AgentControllerObject.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/agent-runtime/test/AgentRuntime.test.ts
  • core/types/agent-runtime/AgentStore.ts

Comment on lines +264 to +268
it('should not record latestRunId when createRun has no threadId', async () => {
const run = await store.createRun([{ role: 'user', content: 'Hello' }]);
assert(run.id.startsWith('run_'));
// No thread to query; the run simply has no latest-run pointer anywhere.
});

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 | ⚡ Quick win

Add a real assertion in this test case.

Line 264’s test name claims pointer behavior, but Lines 265-268 only assert run id format. It won’t fail if pointer logic regresses. Please assert an observable invariant (for example, create a thread first, run without threadId, then assert that thread’s latest run id is still null).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/agent-runtime/test/OSSAgentStore.test.ts` around lines 264 - 268, The
test "should not record latestRunId when createRun has no threadId" currently
only checks run.id; update it to create a thread first (use store.createThread),
then call store.createRun without a threadId, keep the existing assertion that
run.id startsWith('run_'), then retrieve the original thread (e.g.,
store.getThread or store.getThreadById) and assert that the thread's latestRunId
(thread.latestRunId) is still null/undefined to ensure no pointer was recorded.

@akitaSummer

Copy link
Copy Markdown
Contributor

1

@akitaSummer akitaSummer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1

@jerryliang64 jerryliang64 merged commit 14a4f2e into master Jun 10, 2026
12 checks passed
@jerryliang64 jerryliang64 deleted the feat-recent-run-id branch June 10, 2026 03:08
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.

2 participants