-
Notifications
You must be signed in to change notification settings - Fork 0
feat(harness): complete resume loop + event observability #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
fc8183c
836715c
112adc7
3c20bc1
054215f
138cf20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,8 @@ defmodule Harness.SessionManager do | |
| end | ||
|
|
||
| defp start_session_with_module(session_module, thread_id, provider, params) do | ||
| params = maybe_inject_resume_cursor(params, thread_id, provider) | ||
|
|
||
| child_spec = | ||
| {session_module, | ||
| %{ | ||
|
|
@@ -258,6 +260,38 @@ defmodule Harness.SessionManager do | |
| SnapshotServer.apply_event(event) | ||
| end | ||
|
|
||
| @doc false | ||
| # Inject resumeCursor from SQLite binding if one exists and the provider matches. | ||
| # If the caller already supplied a resumeCursor, respect it (don't overwrite). | ||
| def maybe_inject_resume_cursor(%{"resumeCursor" => _} = params, _thread_id, _provider) do | ||
| params | ||
| end | ||
|
|
||
| def maybe_inject_resume_cursor(params, thread_id, provider) do | ||
| case Harness.Storage.get_binding(thread_id) do | ||
| %{provider: ^provider, resume_cursor_json: cursor_json} when is_binary(cursor_json) -> | ||
| case Jason.decode(cursor_json) do | ||
| {:ok, cursor} -> | ||
| normalized = normalize_resume_cursor(cursor) | ||
| Logger.info("Injecting resumeCursor from binding for #{thread_id} (#{provider})") | ||
| Map.put(params, "resumeCursor", normalized) | ||
|
|
||
| {:error, reason} -> | ||
| Logger.warning("Failed to decode binding cursor for #{thread_id}: #{inspect(reason)}") | ||
| params | ||
| end | ||
|
|
||
| _ -> | ||
| # No binding, provider mismatch, or nil cursor — start fresh | ||
| params | ||
| end | ||
| end | ||
|
|
||
| # Codex stores {"threadId": "..."} but CodexSession expects the raw string. | ||
| # Other providers store richer objects their session modules know how to parse. | ||
| defp normalize_resume_cursor(%{"threadId" => tid}) when is_binary(tid), do: tid | ||
| defp normalize_resume_cursor(cursor), do: cursor | ||
|
Comment on lines
+270
to
+293
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify how each session module handles resumeCursor
echo "=== cursor_session.ex: extract_resume_session_id expects binary ==="
rg -n -A 15 'defp extract_resume_session_id' apps/harness/lib/harness/providers/cursor_session.ex
echo ""
echo "=== claude_session.ex: parse_resume_state expects binary ==="
rg -n -A 20 'defp parse_resume_state' apps/harness/lib/harness/providers/claude_session.ex
echo ""
echo "=== opencode_session.ex: check resumeCursor handling ==="
rg -n -B 2 -A 10 'resumeCursor' apps/harness/lib/harness/providers/opencode_session.ex || echo "No resumeCursor handling found"Repository: Ranvier-Technologies/t3code-OTP Length of output: 1794 🏁 Script executed: # Get the full parse_resume_state function from claude_session
sed -n '1021,1080p' apps/harness/lib/harness/providers/claude_session.exRepository: Ranvier-Technologies/t3code-OTP Length of output: 1745 🏁 Script executed: # Get the full extract_resume_session_id function from cursor_session
sed -n '374,410p' apps/harness/lib/harness/providers/cursor_session.exRepository: Ranvier-Technologies/t3code-OTP Length of output: 1270 🏁 Script executed: # Check codex_session for resumeCursor handling
rg -n -B 2 -A 15 'resumeCursor' apps/harness/lib/harness/providers/codex_session.exRepository: Ranvier-Technologies/t3code-OTP Length of output: 671 🏁 Script executed: # List all session provider files to see what modules exist
fd -e ex$ 'session\.ex$' apps/harness/lib/harness/providers/Repository: Ranvier-Technologies/t3code-OTP Length of output: 57 🏁 Script executed: # Check mock_session for resumeCursor handling
rg -n -B 2 -A 10 'resumeCursor' apps/harness/lib/harness/providers/mock_session.ex
# Check if there's an opencode_session file and its resumeCursor handling
fd -e ex$ session apps/harness/lib/harness/providers/ | sortRepository: Ranvier-Technologies/t3code-OTP Length of output: 57 🏁 Script executed: # Check if mock_session even exists
fd -type f -name '*session.ex' apps/harness/lib/harness/providers/ | xargs -I {} basename {}Repository: Ranvier-Technologies/t3code-OTP Length of output: 245 🏁 Script executed: # Verify the current binding storage format to understand if cursor_json is stored as JSON string or map
rg -n 'resume_cursor_json' apps/harness/lib/harness/storage.ex | head -20Repository: Ranvier-Technologies/t3code-OTP Length of output: 1240 🏁 Script executed: # List all session provider files
fd -name '*session.ex' apps/harness/lib/harness/providers/Repository: Ranvier-Technologies/t3code-OTP Length of output: 305 🏁 Script executed: # Check how cursor_session builds its resume cursor to understand what format it stores
rg -n -B 2 -A 10 'build_resume_cursor' apps/harness/lib/harness/providers/cursor_session.exRepository: Ranvier-Technologies/t3code-OTP Length of output: 935 🏁 Script executed: # List all session provider files
fd '*session.ex' apps/harness/lib/harness/providers/Repository: Ranvier-Technologies/t3code-OTP Length of output: 365 🏁 Script executed: # Verify if there's a mock_session file
ls -la apps/harness/lib/harness/providers/ | grep sessionRepository: Ranvier-Technologies/t3code-OTP Length of output: 413 🏁 Script executed: # Check mock_session for resumeCursor handling
rg 'resumeCursor' apps/harness/lib/harness/providers/mock_session.ex
# Check opencode_session for resumeCursor handling
rg 'resumeCursor' apps/harness/lib/harness/providers/opencode_session.exRepository: Ranvier-Technologies/t3code-OTP Length of output: 114 🏁 Script executed: # Now verify the actual issue: let's trace through what normalize_resume_cursor does
# Get the exact implementation from session_manager
sed -n '270,330p' apps/harness/lib/harness/session_manager.exRepository: Ranvier-Technologies/t3code-OTP Length of output: 1523 Type mismatch in The function decodes
The fix: move JSON decoding into Proposed fix def maybe_inject_resume_cursor(params, thread_id, provider) do
case Harness.Storage.get_binding(thread_id) do
%{provider: ^provider, resume_cursor_json: cursor_json} when is_binary(cursor_json) ->
- case Jason.decode(cursor_json) do
- {:ok, cursor} ->
- normalized = normalize_resume_cursor(cursor)
- Logger.info("Injecting resumeCursor from binding for #{thread_id} (#{provider})")
- Map.put(params, "resumeCursor", normalized)
-
- {:error, reason} ->
- Logger.warning("Failed to decode binding cursor for #{thread_id}: #{inspect(reason)}")
- params
- end
+ case normalize_resume_cursor(provider, cursor_json) do
+ {:ok, normalized} ->
+ Logger.info("Injecting resumeCursor from binding for #{thread_id} (#{provider})")
+ Map.put(params, "resumeCursor", normalized)
+
+ :error ->
+ Logger.warning("Failed to decode binding cursor for #{thread_id}")
+ params
+ end
_ ->
# No binding, provider mismatch, or nil cursor — start fresh
params
end
end
-# Codex stores {"threadId": "..."} but CodexSession expects the raw string.
-# Other providers store richer objects their session modules know how to parse.
-defp normalize_resume_cursor(%{"threadId" => tid}) when is_binary(tid), do: tid
-defp normalize_resume_cursor(cursor), do: cursor
+defp normalize_resume_cursor("codex", cursor_json) do
+ case Jason.decode(cursor_json) do
+ {:ok, %{"threadId" => tid}} when is_binary(tid) -> {:ok, tid}
+ _ -> :error
+ end
+end
+defp normalize_resume_cursor(_provider, cursor_json), do: {:ok, cursor_json}🤖 Prompt for AI Agents |
||
|
|
||
| defp provider_module("codex"), do: {:ok, CodexSession} | ||
| defp provider_module("claudeAgent"), do: {:ok, ClaudeSession} | ||
| defp provider_module("opencode"), do: {:ok, OpenCodeSession} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.