Feature Agent sdk build in - #28
Conversation
- Created AgentCLI class with interactive command processing - Implemented CommandResult dataclass for structured command output - Added 10+ command handlers: help, session, memory, workspace, identity, status, exit - Session commands: create, start, pause, resume, info, end - Memory commands: add, list, search, stats, export - Workspace commands: init, status, branch, commit, history, list - Identity commands: load, show, export - Proper error handling with silent failures for CLI safety - Added _get_prompt() method for interactive prompts - All 52 comprehensive test cases passing - Phase 1 (102 tests) still passing - no regressions - Total: 154 tests passing (102 Phase 1 + 52 CLI)
- Updated status to 60% complete (Tasks 1-6 of 10) - Added Task 6 to completed tasks with full details - Updated test results: 154 passing (102 Phase 1 + 52 CLI) - Added CLI to file inventory - Marked Task 6 status as COMPLETE and production-ready
- Created AgentSDK class as primary public API entry point - Provides simplified interface wrapping all managers (Identity, Memory, Workspace, Session, CLI) - Session lifecycle management: create, start, pause, resume, end - Identity management: get, set, export identity - Memory management: add, search, stats, export entries - Workspace operations: init, branch, commit, history, list files, read files - CLI integration: run commands through SDK - Info and utilities: get comprehensive SDK state - All 35 comprehensive test cases passing - Total: 189 tests passing (102 Phase 1 + 52 CLI + 35 SDK) - Zero regressions from previous tasks
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a v0.3.0 Agent SDK foundation (identity, multi-file memory, isolated git workspaces, sessions, task orchestrator, telemetry, interactive CLI), many design/roadmap documents, extensive unit/integration tests, and package version bumps. ChangesAgent SDK + Docs + Tests (v0.3.0)
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as AgentCLI
participant SDK as AgentSDK
participant Session as AgentSessionMgr
participant Memory as AgentMemoryMgr
participant Workspace as AgentWorkspaceMgr
User->>CLI: session create /path [goals]
CLI->>SDK: create_session(path, goals)
SDK->>Session: create_session(...)
Session-->>SDK: session_id
User->>CLI: session start
CLI->>SDK: start_session()
SDK->>Session: start_session(initialize_managers=True)
Session->>Memory: initialize()
Session->>Workspace: initialize_repo()
Memory-->>Session: ready
Workspace-->>Session: ready
Session-->>SDK: started
User->>CLI: memory add "note"
CLI->>SDK: add_memory(content)
SDK->>Memory: add_entry(...)
Memory->>Memory: persist to disk
Memory-->>SDK: MemoryEntry
User->>CLI: workspace commit "msg"
CLI->>SDK: commit_changes(msg)
SDK->>Workspace: commit_changes()
Workspace->>Workspace: git commit
Workspace-->>SDK: commit_hash
User->>CLI: session end
CLI->>SDK: end_session()
SDK->>Session: end_session()
Session->>Session: finalize metrics, export summary
Session-->>SDK: SessionSummary
SDK-->>CLI: SessionSummary
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
- Introduced integration tests for AgentTaskOrchestrator and AgentTelemetryManager, covering task orchestration, telemetry session management, and event recording. - Created unit tests for AgentTaskOrchestrator, focusing on task creation, execution lifecycle, dependency management, and planning. - Developed unit tests for AgentTelemetryManager, validating event tracking, metrics collection, and session management. - Ensured robust error handling and edge case coverage across all tests.
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🟠 Major comments (23)
INTERACTIVE_AGENT_ARCHITECTURE.md-750-754 (1)
750-754:⚠️ Potential issue | 🟠 MajorAvoid exposing raw exception text over WebSocket.
Returning
str(e)to clients can leak internal details. Send a generic client message and log the exception server-side.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@INTERACTIVE_AGENT_ARCHITECTURE.md` around lines 750 - 754, The WebSocket exception handler currently sends raw exception text via websocket.send_json({ "type": "error", "message": str(e) }) which can leak internals; change it to send a generic client-facing error message (e.g., "An internal server error occurred") and move the detailed exception logging server-side by calling your logger's exception or error method (or use logging.exception) inside the except block to record the full stacktrace; keep websocket.send_json limited to the generic message and ensure the except clause remains "except Exception as e" to capture and log the error server-side.AGENT_CAPABILITIES_ARCHITECTURE.md-692-709 (1)
692-709:⚠️ Potential issue | 🟠 MajorPeriodic-save logic has two correctness issues.
Line 703 can crash when
self.last_saveisNone, andlen(self.dirty_files) >= 10is tied to unique filenames (set), not turn count—so the “every 10 messages” trigger likely never behaves as intended.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@AGENT_CAPABILITIES_ARCHITECTURE.md` around lines 692 - 709, In should_save_periodic(), avoid calling (datetime.now() - self.last_save).seconds when self.last_save can be None—first check self.last_save is not None (or treat None as forcing a save) and use (datetime.now() - self.last_save).total_seconds() > 1800 for correct granularity; replace the current len(self.dirty_files) >= 10 trigger (which measures unique filenames) with a proper turn/message counter (e.g., self.message_count or self.turn_count) and check that counter >= 10, and keep the "suggestion_applied" membership check on self.dirty_files as-is; update any places that increment or persist the counter so it reflects message turns rather than set size.BACKEND_ARCHITECTURE_BLUEPRINT.md-632-668 (1)
632-668:⚠️ Potential issue | 🟠 Major
AsyncSessiondoes not support.query()method in SQLAlchemy 2.x—code will fail at runtime.Lines 633–668 use
await db.query(Analysis).filter(...)inget_analysis()andlist_analyses(), which is unsupported. AsyncSession requires the modern 2.0 API:Use
await db.execute(select(Analysis).where(...))then.scalars(), or directly callawait db.scalars(select(Analysis).where(...)).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@BACKEND_ARCHITECTURE_BLUEPRINT.md` around lines 632 - 668, The AsyncSession `.query()` calls in AnalysisService.get_analysis and AnalysisService.list_analyses are invalid for SQLAlchemy 2.x; replace them with the select-based API: import select from sqlalchemy, build a select(Analysis).where(…) (and add .order_by(...).limit(...).offset(...) for list_analyses), then run result = await db.scalars(select_stmt) (or await db.execute(select_stmt) followed by result.scalars()) and return result.first() for get_analysis and result.all() for list_analyses; update the functions (get_analysis, list_analyses) to use these scalar results and adjust imports accordingly.UNIFIED_ARCHITECTURE_BLUEPRINT.md-271-323 (1)
271-323:⚠️ Potential issue | 🟠 MajorRemove the
return responsestatement fromchat_turn— async generators cannot return values.The code snippet mixes
yieldwithreturn response, which is a SyntaxError in Python. Per PEP 525, non-empty return statements are forbidden in async generator functions. Either refactor to useyield responseas the final value or split into a separate async function wrapper.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@UNIFIED_ARCHITECTURE_BLUEPRINT.md` around lines 271 - 323, The async generator chat_turn mixes yields with a non-empty return (return response), which is invalid; remove the final return and instead ensure the final accumulated response is produced via yield (e.g., yield the last chunk or yield the full response before finishing) or convert chat_turn to a regular async function if a returned value is required; update references inside chat_turn (llm_client.stream_completion, session.add_message, prompt building) so you either yield the final response before calling session.add_message or have a separate wrapper that awaits a non-generator helper to get the full response and then calls session.add_message.INTERACTIVE_AGENT_ARCHITECTURE.md-372-377 (1)
372-377:⚠️ Potential issue | 🟠 MajorFix async generator semantics and typing in example code.
The
chat_turnfunction at lines 372–427 mixes async-generator behavior (yield) withreturn response, which is a SyntaxError in Python (PEP 525). Async generators cannot return non-None values. Additionally, line 432 usesDict[str, any]instead of the correctDict[str, Any](capital A).The WebSocket error handler at lines 750–754 also exposes raw exception text to clients, which leaks internal error details.
Suggested fixes
- ) -> str: + ) -> AsyncIterator[str]:- return response + # async generators cannot return values; yield final result instead- ) -> Dict[str, any]: + ) -> Dict[str, Any]:- await websocket.send_json({ - "type": "error", - "message": str(e) - }) + await websocket.send_json({ + "type": "error", + "message": "An error occurred. Contact support for details." + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@INTERACTIVE_AGENT_ARCHITECTURE.md` around lines 372 - 377, The chat_turn function currently mixes async-generator semantics (yield) with returning a value, which is illegal: either make chat_turn an async generator that yields strings and has signature returning AsyncGenerator[str, None] (and remove the final return value), or convert it to a conventional async function that builds and returns a final string (remove all yields); update the signature and imports accordingly and keep AgentSession typing. Fix the typing typo Dict[str, any] to Dict[str, Any] by importing Any from typing. For the WebSocket error handler, stop sending raw exception text to clients: log the full exception internally (use logger.exception) and send a sanitized, non-sensitive error message to the client (e.g., "internal server error") instead of the raw exception.tests/unit/test_agent_workspace.py-117-124 (1)
117-124:⚠️ Potential issue | 🟠 MajorFlaky test: asserts incorrect default branch "develop".
The test expects
get_current_branch()to return"develop"after initialization, butinitialize_repo()runsgit initwithout specifying a branch. This creates the system's default branch (typically "main" in modern git versions), not "develop". The test will fail on any environment where git's default branch is not "develop".The assertion at line 120 should either:
- Assert the actual default branch returned by
git init, or- Explicitly create and checkout the "develop" branch during initialization before testing.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_agent_workspace.py` around lines 117 - 124, The failing test assumes the repo default branch is "develop"; update the test or initialization so it doesn't rely on system git defaults—either modify initialize_repo() to create and checkout a "develop" branch before assertions, or change test_get_current_branch to assert the actual branch returned by get_current_branch() (e.g., capture branch = initialized_workspace.get_current_branch() and assert it's not None or compare to initialized_workspace.get_default_branch()), then create_branch("test-branch") and assert get_current_branch() == "test-branch"; reference functions: initialize_repo, get_current_branch, create_branch, and test_get_current_branch to locate where to apply the fix.src/ghostclaw/core/agent_sdk/agent_cli.py-604-615 (1)
604-615:⚠️ Potential issue | 🟠 MajorList files from the isolated workspace, not from the original project path.
The active workspace lives behind
workspace_mgr, but this command globscurrent_session["project_path"]instead. Users will see a different tree than the session's actual workspace, and manager-level filtering like.gitexclusion is bypassed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 604 - 615, The _workspace_list function is incorrectly globbing self.current_session["project_path"] instead of listing files from the active isolated workspace; update _workspace_list to ask the workspace manager for the active workspace root (via workspace_mgr API such as workspace_mgr.get_active_workspace() or workspace_mgr.root/path property) and glob from that root so manager-level filters (e.g., .git exclusion) are respected, then return the file list converted to strings as before; ensure you reference and use workspace_mgr and not current_session["project_path"] in the implementation.src/ghostclaw/core/agent_sdk/agent_session.py-360-369 (1)
360-369:⚠️ Potential issue | 🟠 MajorStore the same action you return, and count it.
log_action()constructs oneSessionActionto return, then_log_action()constructs a second one with a differentid/timestampand appends that instead. On top of that,SessionMetrics.action_countis never incremented, so callers get an action handle that does not exist in_actionsand metrics that always report zero actions.Also applies to: 539-558
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_session.py` around lines 360 - 369, The code constructs a SessionAction locally and then calls _log_action which builds a separate SessionAction (different id/timestamp) and appends that, so the returned action isn't the one stored and SessionMetrics.action_count is never incremented; fix by having _log_action accept a SessionAction instance (or make _log_action return the stored action) and append the same SessionAction object created in the caller (the one from SessionAction(...)), then increment SessionMetrics.action_count whenever an action is appended to _actions (update both the creation sites around SessionAction and the _log_action signature/usage to use the same object and increase action_count).src/ghostclaw/core/agent_sdk/agent_cli.py-225-238 (1)
225-238:⚠️ Potential issue | 🟠 MajorCheck the manager result before returning
success=True.These handlers ignore falsy return values from the underlying managers. A failed
start_session(),end_session(),initialize_repo(),create_branch(), orcommit_changes()is still reported as success, so the CLI can drift away from the real session/workspace state.
As per coding guidelines, "Use structured reporting via ArchitectureReport model for error handling".Also applies to: 247-260, 269-282, 318-327, 513-518, 554-558, 576-580
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 225 - 238, The CLI handlers currently assume manager methods succeeded and always return success=True; update the start_session handler (check the boolean/result returned by session_manager.start_session()) and similarly fix the other handlers that call end_session(), initialize_repo(), create_branch(), and commit_changes() so they validate the manager result before setting success. If a manager call returns falsy or an error-like result, build and return an ArchitectureReport-based failure response (success=False) with the manager's error/details instead of marking the command successful, and only set current_session["state"]="started" (or other state changes) after the manager call is confirmed successful.src/ghostclaw/core/agent_sdk/agent_sdk.py-360-362 (1)
360-362:⚠️ Potential issue | 🟠 MajorPropagate the manager result instead of always returning
True.
initialize_repo()returnsFalse, andcreate_branch()/commit_changes()returnNoneon failure. These wrappers ignore that and report success anyway, so the SDK can claim a workspace operation succeeded when nothing happened.
As per coding guidelines, "Use structured reporting via ArchitectureReport model for error handling".Also applies to: 379-381, 398-400
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_sdk.py` around lines 360 - 362, The wrapper methods in AgentSDK that call WorkspaceManager (e.g., the method that calls self.workspace_manager.initialize_repo(), and the wrappers around create_branch() and commit_changes()) must stop unconditionally returning True/None; instead propagate the manager return values and produce an ArchitectureReport on failure: check the boolean/None result from initialize_repo(), create_branch(), and commit_changes(), return the actual success value when truthy, and when falsy construct and return an ArchitectureReport containing a clear error message and any underlying manager error info; update the wrapper methods in agent_sdk.py to return or raise the ArchitectureReport per project conventions rather than always returning True or swallowing failures.src/ghostclaw/core/agent_sdk/agent_workspace.py-233-246 (1)
233-246:⚠️ Potential issue | 🟠 MajorThis does not return a commit hash.
For normal
git commitoutput like[main abc1234] my message,line.split()[-1]is the last word of the message, not the hash. Callers will get bogus commit IDs unless you parse the bracketed hash or readHEADafter committing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_workspace.py` around lines 233 - 246, The commit handling in commit (in agent_workspace.py) incorrectly extracts the commit id by taking the last word of a git commit output line; update commit logic in the method that calls self._run_git_command to either parse the bracketed hash from lines like "[branch <hash>]" (e.g., extract the token inside square brackets) or, more robustly, run a separate git command after committing (e.g., git rev-parse HEAD) to obtain the exact commit SHA; ensure you update the code paths that currently iterate result.split('\n') and return line.split()[-1] to instead return the parsed SHA from the bracket or the rev-parse output.src/ghostclaw/core/agent_sdk/agent_workspace.py-116-117 (1)
116-117:⚠️ Potential issue | 🟠 MajorDon't hardcode
developas the active branch.
initialize_repo()never syncs_current_branchwith the repository's real HEAD, so repos onmain,master, or a remote-specific default still look like they're ondevelop.get_status()andpush_changes()then report/push the wrong branch.Also applies to: 139-170
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_workspace.py` around lines 116 - 117, The code currently hardcodes self._current_branch = "develop", causing initialize_repo(), get_status(), and push_changes() to operate on the wrong branch; instead, update initialize_repo() (and any places that set branch like the block around lines 139-170) to read the repository's actual HEAD and set self._current_branch accordingly (e.g., use the git library's active_branch or resolve HEAD and fall back when detached), and ensure any operations in get_status() and push_changes() reference this dynamically populated self._current_branch after checkouts/pulls so the real branch name is used rather than "develop".src/ghostclaw/core/agent_sdk/agent_session.py-271-281 (1)
271-281:⚠️ Potential issue | 🟠 MajorAccount for an in-progress pause before computing
total_duration.If the session is ended directly from
PAUSED,_paused_atis never folded into_paused_duration, so the reported duration includes time that should have been excluded as paused time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_session.py` around lines 271 - 281, When ending a session, ensure any ongoing pause is folded into the paused tally before computing total_duration: if self._paused_at is set (session was PAUSED), add (self._ended_at - self._paused_at) to self._paused_duration and clear/self._paused_at, then compute total_duration using self._ended_at minus self._started_at or self._created_at minus the updated self._paused_duration; update self._metrics.total_duration accordingly (affecting symbols self._paused_at, self._paused_duration, self._ended_at, self._started_at, self._created_at, and self._metrics.total_duration).src/ghostclaw/core/agent_sdk/agent_identity.py-223-235 (1)
223-235:⚠️ Potential issue | 🟠 MajorReject identities that belong to a different agent.
from_dict()saves whateverdata["id"]contains, whileupdate()explicitly rejectsidentity.id != self.agent_id. That lets one manager persist another agent's identity into this agent's directory and breaks the per-agent isolation model.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_identity.py` around lines 223 - 235, from_dict currently accepts any data["id"] and saves it; mirror update()'s behavior by enforcing per-agent isolation: if data contains an "id" that does not equal self.agent_id raise a ValueError (or similar), and otherwise set data["id"] = self.agent_id before constructing AgentIdentity and calling self.save; reference the from_dict method, self.agent_id, self.save, and AgentIdentity when locating where to add this validation.src/ghostclaw/core/agent_sdk/agent_sdk.py-295-311 (1)
295-311:⚠️ Potential issue | 🟠 MajorFix the public return contract for
search_memory().The signature/docstring promise a
List[Dict[str, Any]], butAgentMemoryManager.search_all()returnsDict[str, List[MemoryEntry]]. Callers get a different shape and model instances than this API advertises.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_sdk.py` around lines 295 - 311, The public method search_memory currently returns the raw AgentMemoryManager.search_all() shape (a Dict[str, List[MemoryEntry]]) instead of the advertised List[Dict[str, Any]]; update search_memory to call AgentMemoryManager.search_all(pattern), iterate the returned mapping and flatten/serialize each MemoryEntry into plain dicts (e.g., using a to_dict()/as_dict() method or manual field extraction) producing a single List[Dict[str, Any]] of entries, preserve any relevant metadata (source/key) in each dict, and keep the current None/exception handling (return [] when memory_manager is missing or on error); reference search_memory and AgentMemoryManager.search_all to locate and implement the transformation.src/ghostclaw/core/agent_sdk/agent_memory.py-261-273 (1)
261-273:⚠️ Potential issue | 🟠 MajorRebuild
INDEX.mdafter any destructive memory change.
add_entry()appends to the index, butupdate_entry(),delete_entry(), andclear_memory()only rewrite the source file. After a rename, delete, or bulk clear, the catalog still points at entries that no longer exist.Also applies to: 294-299, 438-455
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_memory.py` around lines 261 - 273, The index (INDEX.md) isn't being rebuilt after destructive operations, so update_entry, delete_entry, and clear_memory must trigger an index rebuild like add_entry does; after modifying or removing entries in update_entry (especially on title change/rename), in delete_entry, and in clear_memory call the same index-rebuilding/saving routine that add_entry uses (invoke the method that generates and writes the INDEX.md, or call the existing _save_memory_file/_build_index helper used by add_entry) immediately after persisting the updated memory file so the catalog stays consistent with the source files.src/ghostclaw/core/agent_sdk/agent_workspace.py-326-349 (1)
326-349:⚠️ Potential issue | 🟠 MajorThe
git logtimestamp parsing makes valid entries unreadable.
%aiproduces values like2026-04-03 12:34:56 +0000; replacing every space withTyields2026-04-03T12:34:56T+0000, whichdatetime.fromisoformat()cannot parse. Because the whole method sits under onetry, a single normal log line makesget_commit_history()return[].🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_workspace.py` around lines 326 - 349, The timestamp parsing in the commit loop is incorrect: don't blindly replace all spaces before calling datetime.fromisoformat; instead parse the `%ai` output from self._run_git_command correctly (use datetime.strptime with the format "%Y-%m-%d %H:%M:%S %z" or replace only the first space between date and time to produce an ISO-like "YYYY-MM-DDTHH:MM:SS ±ZZZZ" before calling datetime.fromisoformat). Update the code around the loop that builds GitCommit (the section using parts[3] and datetime.fromisoformat) so timestamps like "2026-04-03 12:34:56 +0000" are parsed without producing errors; keep using GitCommit, and ensure the method (the code calling _run_git_command) returns commits instead of swallowing valid entries.src/ghostclaw/core/agent_sdk/agent_sdk.py-435-460 (1)
435-460:⚠️ Potential issue | 🟠 MajorDelegate workspace file access to
workspace_manager.These methods bypass the isolated workspace entirely and operate on the original
project_pathinstead. That breaks the PR's workspace-isolation contract, and because_project_pathis not cleared onend_session(), file access can continue after the session is closed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_sdk.py` around lines 435 - 460, The listing and reading functions (e.g., the code using self._project_path and the method read_workspace_file) must be changed to delegate file access to the workspace manager instead of touching _project_path directly: check if self._workspace_manager exists and call its public APIs (e.g., workspace manager methods to list files by pattern and to read a workspace file / get file contents) and return [] or None if the manager is missing or returns nothing; remove direct uses of self._project_path in list and read flows so file access honors workspace isolation and stops after end_session().src/ghostclaw/core/agent_sdk/agent_memory.py-208-233 (1)
208-233:⚠️ Potential issue | 🟠 MajorFail fast on unknown memory types in read paths too.
add_entry()validatesmemory_type, butget_entries()silently manufactures an emptyMemoryFilefor typos like"LONGTERM". That turns a bad caller into a fake "no results" response and already hides real data from higher layers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_memory.py` around lines 208 - 233, The read path must validate memory_type and fail fast instead of creating an empty MemoryFile; before calling self._load_memory_file(memory_type) in get_entries (the shown method), invoke the same validation used by add_entry (e.g., call self._validate_memory_type(memory_type) or check against the MEMORY_TYPES constant) and raise a ValueError (or propagate the same error type add_entry uses) for unknown types so typos like "LONGTERM" surface as errors rather than returning silent empty results.src/ghostclaw/core/agent_sdk/agent_session.py-158-169 (1)
158-169:⚠️ Potential issue | 🟠 MajorReset all session-scoped state when creating a new session.
This only replaces
_session_id,_created_at, and_state. Previous_context, timestamps, managers,_actions,_metrics, and_paused_durationleak into the next session, so reusing oneAgentSessionManagermixes histories and produces wrong durations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_session.py` around lines 158 - 169, When creating a new session in AgentSession (the block that sets _session_id, _created_at, and _state), also fully reset all session-scoped fields so no previous-session data leaks: reinitialize or clear self._context (or set a fresh Context if none provided), reset any timestamps used for durations, reset/clear collections like self._actions and self._metrics, zero or reset self._paused_duration and pause-related timestamps, and reinitialize any per-session managers (e.g., task/agent managers) to new instances; then call _save_session_metadata() as before. Ensure you reference and reset the exact attributes _session_id, _created_at, _state, _context, _actions, _metrics, _paused_duration and any manager attributes in the same initializer method.src/ghostclaw/core/agent_sdk/agent_session.py-421-443 (1)
421-443:⚠️ Potential issue | 🟠 MajorPersist session timestamps as UTC
Ztimestamps.Both the exported payload and the on-disk session file use bare
isoformat()strings from naive/local datetimes. That makes recovery ambiguous across hosts and violates the repository's timestamp format rule.
As per coding guidelines, "Use UTC ISO format with 'Z' suffix for timestamps".Also applies to: 565-585
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_session.py` around lines 421 - 443, The payload currently uses naive/local isoformat() strings; convert all datetime fields to UTC ISO strings with a trailing 'Z' by ensuring datetimes are timezone-aware and normalized to UTC before formatting—e.g., for self._created_at, self._started_at, self._ended_at and each action timestamp (a.timestamp) call astimezone(datetime.timezone.utc) (or set tzinfo=datetime.timezone.utc for naive datetimes) and then format with .isoformat().replace("+00:00", "Z") (or equivalent) so the exported "created_at", "started_at", "ended_at" and action "timestamp" values (and the other similar block at lines referenced 565-585) are UTC Z-suffixed ISO strings.src/ghostclaw/core/agent_sdk/agent_memory.py-32-46 (1)
32-46:⚠️ Potential issue | 🟠 MajorPersist memory timestamps in UTC with a
Zsuffix.These models stamp entries/files with
datetime.now()and export them with bareisoformat(), so the persisted memory data is local-time/naive. That makes cross-machine ordering ambiguous and violates the repo-wide timestamp contract.
As per coding guidelines, "Use UTC ISO format with 'Z' suffix for timestamps".Also applies to: 353-372
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_memory.py` around lines 32 - 46, Memory timestamps are currently naive/local (using datetime.now) which breaks the UTC-`Z` contract; update the created_at and updated_at Field default_factories in MemoryEntry and MemoryFile to produce timezone-aware UTC datetimes (e.g., use datetime.now(timezone.utc) or equivalent) and ensure any serialization/export path for these models emits ISO-8601 UTC strings with a trailing "Z" (replace "+00:00" with "Z" or use a formatter that outputs 'Z'); update the same pattern referenced at lines 353-372 as well so all persisted timestamps are UTC with the Z suffix.src/ghostclaw/core/agent_sdk/agent_sdk.py-477-485 (1)
477-485:⚠️ Potential issue | 🟠 MajorExpose a created session to the CLI before it becomes active.
After
create_session(),_session_activeis stillFalse, so this setscli.current_session = None. A follow-uprun_cli_command("session start")then fails with "No session active" even though the SDK already created one.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_sdk.py` around lines 477 - 485, After create_session() the CLI is not informed about the newly created session because _session_active is still False; update the assignment logic around self.cli.session_manager/self.cli.session_id/self.cli.current_session (where session_manager.get_session_id() is called) so that cli.current_session is populated from the available session info (project_name, project_path, and the session_id from session_manager) as soon as a session object/id exists, not only when _session_active is True; in practice set self.cli.session_manager = self.session_manager, set self.cli.session_id = self.session_manager.get_session_id(), and compute self.cli.current_session based on whether a session_id (or the session_manager) is present rather than relying on _session_active (i.e., populate current_session when session_id is truthy so subsequent run_cli_command("session start") sees the created session).
🟡 Minor comments (11)
TASK.md-25-33 (1)
25-33:⚠️ Potential issue | 🟡 MinorDocumentation inconsistency: "agent-sdk" vs "agent_sdk".
The document references
src/ghostclaw/core/agent-sdk/with a hyphen, but Python module directories cannot contain hyphens (they cause import errors). The actual module uses underscores:src/ghostclaw/core/agent_sdk/.This inconsistency appears throughout the document (lines 46-60, 84, 102-104, 126, 168, 198, 227, 251, 282, 346, etc.).
📝 Proposed fix
### Architecture (Agent-SDK) -src/ghostclaw/core/agent-sdk/ +src/ghostclaw/core/agent_sdk/ ├── __init__.py ├── agent_identity.py # Agent personality, goals, strengthsApply similar fixes throughout the document.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@TASK.md` around lines 25 - 33, The documentation references the Python package directory using a hyphen ("agent-sdk") which is invalid for imports; update all occurrences to the correct underscore form "agent_sdk" (e.g., in listings and references such as src/ghostclaw/core/agent_sdk/, and any mentions of modules like agent_identity.py, agent_memory.py, agent_workspace.py, agent_session.py) so the docs match the actual package layout; apply this replacement consistently across the document wherever "agent-sdk" appears.CLI_VS_SERVICE_DECISION.md-124-124 (1)
124-124:⚠️ Potential issue | 🟡 MinorTypo: "Kuberenetes" should be "Kubernetes".
📝 Proposed fix
-- If you have a **hosted backend** (Kuberenetes, Cloud Run) +- If you have a **hosted backend** (Kubernetes, Cloud Run)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CLI_VS_SERVICE_DECISION.md` at line 124, Replace the misspelled word "Kuberenetes" with the correct spelling "Kubernetes" in the sentence that starts "If you have a **hosted backend** (Kuberenetes, Cloud Run)" so the text reads "If you have a **hosted backend** (Kubernetes, Cloud Run)"; update the string exactly where it appears in the CLI_VS_SERVICE_DECISION.md content.src/ghostclaw/core/agent_sdk/models.py-215-223 (1)
215-223:⚠️ Potential issue | 🟡 MinorReplace
datetime.utcnow()withdatetime.now(timezone.utc)—it's deprecated in Python 3.12+ and conflicts with coding guidelines requiring UTC ISO format with 'Z' suffix.Update the import and replace all 9 occurrences (lines 216, 221, 290, 425, 447, 452, 520, 525, 633):
♻️ Proposed fix
-from datetime import datetime +from datetime import datetime, timezone # Replace all occurrences of: - default_factory=datetime.utcnow, + default_factory=lambda: datetime.now(timezone.utc),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/models.py` around lines 215 - 223, Replace uses of datetime.utcnow() with datetime.now(timezone.utc) and update the import to bring timezone into scope (e.g., from datetime import datetime, timezone); specifically change default_factory=datetime.utcnow to default_factory=lambda: datetime.now(timezone.utc) for the model fields like created_at and updated_at and any other fields using datetime.utcnow (all occurrences in the models module), ensuring you update all nine occurrences so timestamps are timezone-aware and serialize with UTC 'Z' semantics.src/ghostclaw/core/agent_sdk/models.py-299-304 (1)
299-304:⚠️ Potential issue | 🟡 MinorModernize datetime serialization using Pydantic v2 patterns.
The
json_encodersconfig using dict-stylemodel_configis a Pydantic v1 pattern and is deprecated in Pydantic v2 (currently at 2.12.5). While the code defines inconsistent datetime handling between the unconditional"Z"append here and the conditional append inAgentSDKEncoder.default(), thejson_encoderspath is not invoked by Pydantic v2'smodel_dump_json().Replace this with Pydantic v2's
@field_serializerdecorator or a custom type usingPlainSerializerto properly define datetime serialization. The actual serialization currently relies onAgentSDKEncoderwhich conditionally appends"Z"only for naive datetimes—align the datetime handling across all serialization paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/models.py` around lines 299 - 304, The current use of model_config["json_encoders"] is a Pydantic v1 pattern and is not used by model_dump_json(); instead replace it by adding a Pydantic v2 field serializer (using `@field_serializer`) or a custom PlainSerializer for datetime on the model(s) that currently define model_config so datetime formatting is consistent with AgentSDKEncoder.default(): implement a serializer that appends "Z" only for naive datetimes and preserves timezone-aware datetimes, remove or stop relying on the old model_config dict, and ensure model_dump_json() and AgentSDKEncoder use the same datetime logic so serialization is consistent across code paths.BACKEND_ARCHITECTURE_BLUEPRINT.md-372-373 (1)
372-373:⚠️ Potential issue | 🟡 MinorUse callable defaults for mutable JSON fields.
default=[]/default={}in ORM examples should bedefault=list/default=dictto avoid shared mutable-default pitfalls.Also applies to: 393-394
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@BACKEND_ARCHITECTURE_BLUEPRINT.md` around lines 372 - 373, The JSON Column definitions use mutable defaults (e.g., errors = Column(JSON, default=[])); change these to callable defaults to avoid shared mutable state by replacing default=[] with default=list and any default={} with default=dict for the JSON columns (e.g., the errors Column and the other JSON fields referenced around lines 393-394).PHASE1_PROGRESS.md-396-433 (1)
396-433:⚠️ Potential issue | 🟡 MinorRemove duplicate “Continuation Notes” section.
This repeats content already documented earlier and risks divergence in future updates.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PHASE1_PROGRESS.md` around lines 396 - 433, Remove the duplicated "Continuation Notes" section by deleting the repeated block that begins with the "Continuation Notes" header and the "For Next Session" subsection (including the "Task 6 Status (AgentCLI) - ✅ COMPLETE" and its Implementation Details / Key Learnings content), keeping only the original earlier instance; ensure any references or TOC entries still point to the retained "Continuation Notes" header and that no other content was accidentally removed when deleting the duplicate.PHASE1_PROGRESS.md-3-6 (1)
3-6:⚠️ Potential issue | 🟡 MinorTop-of-file status appears outdated.
The header still reports 60%/154 tests, but this PR context includes SDK unification and 189 passing tests. Please sync this summary block to the latest state.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PHASE1_PROGRESS.md` around lines 3 - 6, Update the top-of-file summary block values to reflect the current PR state: change "**Status**" to the correct percent/step count for the new progress, update "**Tests**" to "189 PASSING" (or "189/189 PASSING" if total known), set "**Last Updated**" to the current date, and revise "**Current Phase**" to indicate "SDK Unification Complete" (or the accurate phase name). Edit the header lines that contain the bold keys ("**Status**", "**Tests**", "**Last Updated**", "**Current Phase**") to keep formatting consistent and ensure the numbers/phase match the PR and CI results.UNIFIED_ARCHITECTURE_BLUEPRINT.md-194-195 (1)
194-195:⚠️ Potential issue | 🟡 MinorWebSocket endpoint path is inconsistent across sections.
The document alternates between
/ws/agent/{session_id},/api/v1/ws/sessions/{id}, and/api/v1/agent. Please standardize one canonical route to avoid implementation drift.Also applies to: 605-607, 779-780
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@UNIFIED_ARCHITECTURE_BLUEPRINT.md` around lines 194 - 195, Choose a single canonical WebSocket route (use /api/v1/ws/sessions/{id}) and replace all inconsistent occurrences of /ws/agent/{session_id}, /api/v1/agent, and /api/v1/ws/sessions/{id} in the document so every endpoint, header, example, and reference consistently uses /api/v1/ws/sessions/{id}; update any descriptive text, sample requests/responses, and section titles that reference the old routes (e.g., the ENDPOINT 2 header and other scattered mentions) to match the chosen canonical path.CHANGELOG.md-64-71 (1)
64-71:⚠️ Potential issue | 🟡 MinorKnown limitations section appears stale relative to this PR’s delivered scope.
Line 66 still says “Tasks 2-10” are pending, but this PR introduces completed SDK/session/CLI work and passing SDK tests. Please update this section so release notes don’t understate shipped functionality.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` around lines 64 - 71, Update the "Known Limitations" section in CHANGELOG.md to remove or revise the stale "Tasks 2-10" pending statement and reflect that SDK, session, and CLI work (including passing SDK tests) introduced by this PR are completed; specifically edit the "Known Limitations" bullet that mentions "Agent system implementation pending (Tasks 2-10)" to either remove "Tasks 2-10" or split that bullet to mark "SDK/session/CLI completed — SDK tests passing" and retain only the still-pending items (e.g., memory persistence, chat turn integration awaiting GhostAgent extension, Git workspace logic in progress, GitHub PR creation API implementation pending) and update the "Mission Control dashboard" note accordingly so the release notes accurately state shipped functionality under the "Known Limitations" heading.REVIEW_FRONEND.md-1-1 (1)
1-1:⚠️ Potential issue | 🟡 MinorFilename typo: "FRONEND" should be "FRONTEND".
The file is named
REVIEW_FRONEND.mdbut should beREVIEW_FRONTEND.mdfor discoverability and consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@REVIEW_FRONEND.md` at line 1, Rename the wrongly spelled documentation file REVIEW_FRONEND.md to REVIEW_FRONTEND.md and update any references to it (README links, docs index, CI configurations, or other markdown links) so they point to REVIEW_FRONTEND.md; ensure any tooling or scripts that expect the old filename are updated to the new name to avoid broken links or missing-file errors.REVIEW.md-454-454 (1)
454-454:⚠️ Potential issue | 🟡 MinorFix typo: "buildverification" should be "build verification".
📝 Suggested fix
-- **CI/CD Pipeline**: Lint, test, buildverification, automated releases +- **CI/CD Pipeline**: Lint, test, build verification, automated releases🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@REVIEW.md` at line 454, Fix the typo in REVIEW.md by replacing the single-word "buildverification" with two words "build verification" in the CI/CD Pipeline bullet (the line containing "**CI/CD Pipeline**: Lint, test, buildverification, automated releases"); search the file for other occurrences of "buildverification" and update them similarly to maintain consistency.
🧹 Nitpick comments (14)
src/ghostclaw/core/agent_sdk/config.py (1)
199-204: Consider thread safety for the singleton accessor.The
get_settings()function has a potential race condition if called concurrently from multiple threads. While this is unlikely to cause issues in typical CLI usage, it could matter in async/service contexts.♻️ Optional: Thread-safe singleton pattern
+import threading + # Global settings instance _settings: Optional[AgentSDKSettings] = None +_settings_lock = threading.Lock() def get_settings() -> AgentSDKSettings: """Get global agent-sdk settings (singleton).""" global _settings if _settings is None: - _settings = AgentSDKSettings() + with _settings_lock: + if _settings is None: + _settings = AgentSDKSettings() return _settings🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/config.py` around lines 199 - 204, The get_settings() singleton accessor can race under concurrent calls; make it thread-safe by introducing a module-level lock (e.g., _settings_lock = threading.Lock()) and guard the creation path in get_settings() with that lock (or use double-checked locking: check _settings, acquire _settings_lock, re-check _settings, then assign AgentSDKSettings()). Update references to get_settings, _settings, and AgentSDKSettings accordingly so only the creation is serialized.CLI_VS_SERVICE_DECISION.md (1)
24-34: Add language specifiers to fenced code blocks.Multiple code blocks lack language identifiers, which triggers markdownlint MD040 warnings. Adding language specifiers improves syntax highlighting and documentation quality.
📝 Example fix for ASCII diagrams
-``` +```text PyPI Package: ghostclaw (CLI) ├── src/ghostclaw/cli/Use
textorplaintextfor ASCII diagrams,pythonfor Python code,bashfor shell commands, etc.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CLI_VS_SERVICE_DECISION.md` around lines 24 - 34, The fenced diagram in CLI_VS_SERVICE_DECISION.md (the block containing "PyPI Package: ghostclaw (CLI)" and "Docker Service: ghostclaw-backend") lacks a language specifier, causing markdownlint MD040 warnings; update the opening fence to include a language like text or plaintext (e.g., change ``` to ```text) so the ASCII tree is properly marked, and do the same for any other unannotated fenced blocks in this file.tests/unit/test_agent_identity.py (2)
163-164:__main__test runner block is unnecessary in pytest suites.This can be removed to keep the module purely collection-driven.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_agent_identity.py` around lines 163 - 164, Remove the unnecessary test-runner boilerplate by deleting the "__main__" block that calls pytest.main([__file__, "-v"]); specifically remove the if __name__ == "__main__": ... pytest.main(...) lines so the module is collection-driven and relies on pytest to discover tests rather than invoking pytest via __file__ and "-v".
22-47: Refactor repeatedget_settingsmonkeypatching into a shared fixture.The current pattern is repeated in every test. A
pytestfixture usingmonkeypatchwill reduce duplication and prevent accidental leakage between tests.Also applies to: 54-82, 88-107, 113-134, 140-160
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_agent_identity.py` around lines 22 - 47, Refactor the repeated TemporaryDirectory + manual monkeypatching of get_settings into a pytest fixture that uses the monkeypatch fixture to override ghostclaw.core.agent_sdk.agent_identity.get_settings to return a MockSettings (with memory_base_dir set to Path(tmpdir)); update tests that currently instantiate AgentIdentityManager and call manager.create (the blocks referencing TemporaryDirectory, MockSettings, original_get_settings, and identity_module) to use the new fixture instead of repeating the patching and restore logic so no manual reassignment of identity_module.get_settings or try/finally is needed.MISSION_CONTROL_ARCHITECTURE.md (1)
21-55: Addtextlanguage identifiers to fenced architecture blocks.This addresses recurring MD040 warnings and improves consistent rendering in markdown tooling.
Also applies to: 90-130, 218-249
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@MISSION_CONTROL_ARCHITECTURE.md` around lines 21 - 55, Add explicit "text" language identifiers to the fenced code blocks in MISSION_CONTROL_ARCHITECTURE.md so the ASCII architecture diagrams stop triggering MD040; locate the triple-backtick blocks that contain the ASCII diagrams (e.g., the top diagram block shown between lines ~21-55 and the other blocks around 90-130 and 218-249) and change their opening fences from ``` to ```text for each affected block (ensure all fenced diagram blocks are updated).PHASE1_PROGRESS.md (1)
113-127: Clean up markdownlint violations in summary/table blocks.Add a language to fenced blocks (e.g.,
text) and ensure blank lines around tables for stable doc linting.Also applies to: 158-163
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@PHASE1_PROGRESS.md` around lines 113 - 127, The markdown has lint violations in the summary and table blocks: add a language identifier (e.g., text) to the fenced block containing "✅ 154 TESTS PASSING (0 FAILURES)" and make sure there is a blank line before and after that fenced block and before and after the Markdown table; apply the same fixes to the other summary/table occurrence mentioned (around the later summary block). This ensures fenced blocks look like ```text ... ``` and tables are separated by blank lines for stable markdownlint behavior.UNIFIED_ARCHITECTURE_BLUEPRINT.md (1)
14-46: Add language tags to fenced non-code blocks.Use
textfor ASCII diagrams and route listings to satisfy markdownlint and keep rendering predictable.Also applies to: 68-99, 107-152
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@UNIFIED_ARCHITECTURE_BLUEPRINT.md` around lines 14 - 46, The fenced blocks in UNIFIED_ARCHITECTURE_BLUEPRINT.md that contain ASCII diagrams and route listings are missing language tags; update each triple-backtick fence surrounding non-code content (the big platform diagram and the other diagram/route listing blocks) to use ```text so markdownlint is satisfied and rendering stays predictable, and apply the same change to the other similar fenced blocks in this file (the remaining diagram/route-listing sections referenced in the review).BACKEND_ARCHITECTURE_BLUEPRINT.md (1)
13-31: Label non-code fenced blocks withtext.This will clear repeated MD040 warnings and improve markdown tooling compatibility.
Also applies to: 51-93
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@BACKEND_ARCHITECTURE_BLUEPRINT.md` around lines 13 - 31, The Markdown contains unlabeled fenced blocks used for plain-text diagrams (e.g., the service and CLI trees shown in BACKEND_ARCHITECTURE_BLUEPRINT.md); change those fences from plain ``` to labeled code fences using the text info string (```text) so they are explicitly marked as non-code blocks. Locate the diagram blocks (examples include the ghostclaw CLI/Backend trees shown around the API/Business Logic/Execution/Storage headings) and update each opening fence to ```text and keep the content unchanged; apply the same change to the other occurrences referenced (the other diagram blocks around the file).INTERACTIVE_AGENT_ARCHITECTURE.md (1)
11-40: Add language identifiers to fenced diagram blocks.Use
```textfor ASCII diagrams so markdownlint passes consistently.Also applies to: 46-66
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@INTERACTIVE_AGENT_ARCHITECTURE.md` around lines 11 - 40, The fenced ASCII diagram blocks in INTERACTIVE_AGENT_ARCHITECTURE.md lack language identifiers; update each triple-backtick fence that encloses the ASCII diagrams (the diagram starting with "GHOSTCLAW PLATFORM" and the other diagram around lines 46-66) to use a text language tag by changing ``` to ```text so markdownlint recognizes them as plain text; ensure both opening fences are updated (leave closing fences as ```) and run a quick lint to confirm the warning is resolved.CHANGELOG.md (1)
77-85: Add a language tag to the fenced migration block.Use
```textfor this block to satisfy markdownlint and keep docs tooling consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` around lines 77 - 85, The fenced migration block containing the version ladder (the lines starting with "v0.2.5 (Legacy CLI, current production)" through "v1.0.0 (Production Agent System)") should have a language tag added to the opening fence; change the opening ``` to ```text so markdownlint and docs tooling recognize it as plain text and the block is properly linted.AGENT_CAPABILITIES_ARCHITECTURE.md (1)
20-43: Add language tags for fenced diagram blocks.Use
textfor these blocks to resolve MD040 and keep docs lint-clean.Also applies to: 108-122
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@AGENT_CAPABILITIES_ARCHITECTURE.md` around lines 20 - 43, The fenced ASCII diagram blocks (e.g., the "Agent Identity Lifecycle" diagram and the other similar diagram later in the file) are missing language tags and trigger MD040; add the language tag "text" to those triple-backtick fenced blocks (```text ... ```) so the diagrams are treated as plain text and the markdown lint warning is resolved.REVIEW_FRONEND.md (2)
53-64: Add language specifier to fenced code block.The code block describing the user flow should have a language specifier (e.g.,
textorplaintext) for better markdown rendering consistency.📝 Suggested fix
-``` +```text Home Page (/page.tsx)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@REVIEW_FRONEND.md` around lines 53 - 64, The fenced code block in REVIEW_FRONEND.md that shows the user flow lacks a language specifier; update that triple-backtick block to include a plaintext specifier (for example use ```text or ```plaintext) so the flow (lines like "Home Page (/page.tsx)" through "Results Display (AnalysisForm)") renders consistently in Markdown-aware renderers.
111-135: Add language specifier to data flow diagram code block.Similar to the user flow block, this diagram should have a language specifier.
📝 Suggested fix
-``` +```text Frontend Form Input🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@REVIEW_FRONEND.md` around lines 111 - 135, The diagram code block lacks a language specifier; edit the Markdown block that contains the flow starting with "Frontend Form Input" and add a language tag (e.g., ```text) after the opening backticks so the block matches the other user flow block and renders consistently.tests/unit/test_agent_memory.py (1)
418-444: Make this cutoff test assert real behavior.
assert deleted >= 0will pass even ifclear_memory(before_date=...)stops deleting old entries entirely. Because this test mutates the cachedMemoryEntryinstance, it can deterministically assert that exactly one old entry is removed and the recent entry remains.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_agent_memory.py` around lines 418 - 444, Update the test to assert deterministic behavior: after creating old_entry and setting old_entry.created_at = datetime.now() - timedelta(days=30), call memory_manager.clear_memory(memory_type=memory_manager.CONTEXT_FILE, before_date=cutoff_date) and assert deleted == 1; then verify the recent entry still exists (e.g., via memory_manager.list_entries or memory_manager.get_entry using recent_entry.id) and that only the old entry was removed. Ensure you reference add_entry, clear_memory, memory_manager.CONTEXT_FILE, and the mutated MemoryEntry.created_at in the test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ecd92b9f-c143-4aca-980c-933071b2855e
📒 Files selected for processing (33)
AGENT_CAPABILITIES_ARCHITECTURE.mdBACKEND_ARCHITECTURE_BLUEPRINT.mdCHANGELOG.mdCLI_VS_SERVICE_DECISION.mdINTERACTIVE_AGENT_ARCHITECTURE.mdKNOWLEDGE_DB_ARCHITECTURE.mdMISSION_CONTROL_ARCHITECTURE.mdPHASE1_PROGRESS.mdREVIEW.mdREVIEW_FRONEND.mdTASK.mdUNIFIED_ARCHITECTURE_BLUEPRINT.mdpackage.jsonpyproject.tomlsrc/ghostclaw/core/agent_sdk/__init__.pysrc/ghostclaw/core/agent_sdk/agent_cli.pysrc/ghostclaw/core/agent_sdk/agent_identity.pysrc/ghostclaw/core/agent_sdk/agent_memory.pysrc/ghostclaw/core/agent_sdk/agent_sdk.pysrc/ghostclaw/core/agent_sdk/agent_session.pysrc/ghostclaw/core/agent_sdk/agent_workspace.pysrc/ghostclaw/core/agent_sdk/config.pysrc/ghostclaw/core/agent_sdk/models.pysrc/ghostclaw/core/agent_sdk/py.typedsrc/ghostclaw/core/agent_sdk/serializers.pysrc/ghostclaw/version.pytests/unit/test_agent_cli.pytests/unit/test_agent_identity.pytests/unit/test_agent_memory.pytests/unit/test_agent_sdk.pytests/unit/test_agent_sdk_foundation.pytests/unit/test_agent_session.pytests/unit/test_agent_workspace.py
| assert metadata.type == AgentType.CLI | ||
| assert metadata.type == AgentType.GENERALIST |
There was a problem hiding this comment.
Test references non-existent enum value AgentType.GENERALIST.
Per the enum definition in src/ghostclaw/core/agent_sdk/models.py, AgentType only contains CLI, SERVICE, and MISSION_CONTROL. The assertion AgentType.GENERALIST will raise an AttributeError.
🐛 Proposed fix
assert metadata.type == AgentType.CLI
- assert metadata.type == AgentType.GENERALIST📝 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.
| assert metadata.type == AgentType.CLI | |
| assert metadata.type == AgentType.GENERALIST | |
| assert metadata.type == AgentType.CLI |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_agent_sdk_foundation.py` around lines 261 - 262, The test is
asserting a non-existent enum value AgentType.GENERALIST which will raise
AttributeError; update the assertion in tests/unit/test_agent_sdk_foundation.py
that references metadata.type so it uses a valid enum from
src/ghostclaw/core/agent_sdk/models.py (AgentType.CLI, AgentType.SERVICE or
AgentType.MISSION_CONTROL) — either replace AgentType.GENERALIST with the
intended valid value (e.g., AgentType.SERVICE or AgentType.MISSION_CONTROL) or
change the assertion to check membership against the allowed set (e.g., assert
metadata.type in {AgentType.CLI, AgentType.SERVICE, AgentType.MISSION_CONTROL}).
| def test_model_to_json_dict(self): | ||
| """Test model_to_json_dict function.""" | ||
| agent_id = uuid4() | ||
| metaname="test-agent", | ||
| type=AgentType.CLI, | ||
| status=AgentStatus.ACTIVE, | ||
| ) | ||
| data_dict = model_to_json_dict(metadata) | ||
| assert isinstance(data_dict, dict) | ||
| assert data_dict["id"] == str(agent_id) | ||
| assert data_dict["type"] == "cli | ||
| assert data_dict["id"] == str(agent_id) | ||
| assert data_dict["type"] == "specialist" |
There was a problem hiding this comment.
Critical syntax errors in test method.
This test method contains multiple syntax errors:
- Line 267:
metaname=instead ofmetadata = AgentMetadata(name= - Line 274: Unclosed string
"cli - Line 276: References non-existent
AgentType.specialist
These errors will prevent the test suite from running.
🐛 Proposed fix
def test_model_to_json_dict(self):
"""Test model_to_json_dict function."""
agent_id = uuid4()
- metaname="test-agent",
+ metadata = AgentMetadata(
+ id=agent_id,
+ name="test-agent",
type=AgentType.CLI,
status=AgentStatus.ACTIVE,
)
data_dict = model_to_json_dict(metadata)
assert isinstance(data_dict, dict)
assert data_dict["id"] == str(agent_id)
- assert data_dict["type"] == "cli
- assert data_dict["id"] == str(agent_id)
- assert data_dict["type"] == "specialist"
+ assert data_dict["type"] == "cli"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_agent_sdk_foundation.py` around lines 264 - 276, Fix the
syntax and logic in the test_model_to_json_dict test: replace the malformed
metadata assignment with a proper AgentMetadata construction (use
AgentMetadata(name=..., type=AgentType.CLI, status=AgentStatus.ACTIVE)), ensure
the expected type string assertion matches the chosen enum (e.g., assert
data_dict["type"] == "cli" if using AgentType.CLI) and close any open string
literals, and remove duplicate/conflicting assertions; update references to use
valid enums (AgentType.CLI or the actual enum member for "specialist" if it
exists) so model_to_json_dict, AgentMetadata, AgentType, and AgentStatus are
used consistently and syntactically correct.
| def test_json_dict_to_model(self): | ||
| """Test json_dict_to_model function.""" | ||
| agentname": "test-agent", | ||
| "type": "cli", | ||
| "status": "idle", | ||
| "version": "0.3.0", | ||
| } | ||
| metadata = json_dict_to_model(data_dict, AgentMetadata) | ||
| assert metadata.id == agent_id | ||
| assert metadata.name == "test-agent" | ||
| assert metadata.type == AgentType.CLI | ||
| metadata = json_dict_to_model(data_dict, AgentMetadata) | ||
| assert metadata.id == agent_id | ||
| assert metadata.type == AgentType.GENERALIST |
There was a problem hiding this comment.
Critical syntax errors in test_json_dict_to_model.
Multiple syntax errors:
- Line 280:
agentname":instead of proper dict syntax - Lines 289-291: Duplicate assertions with non-existent enum
AgentType.GENERALIST
🐛 Proposed fix
def test_json_dict_to_model(self):
"""Test json_dict_to_model function."""
- agentname": "test-agent",
+ agent_id = uuid4()
+ data_dict = {
+ "id": str(agent_id),
+ "name": "test-agent",
"type": "cli",
"status": "idle",
"version": "0.3.0",
}
metadata = json_dict_to_model(data_dict, AgentMetadata)
assert metadata.id == agent_id
assert metadata.name == "test-agent"
assert metadata.type == AgentType.CLI
- metadata = json_dict_to_model(data_dict, AgentMetadata)
- assert metadata.id == agent_id
- assert metadata.type == AgentType.GENERALIST🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_agent_sdk_foundation.py` around lines 278 - 291, The
test_json_dict_to_model test contains malformed JSON/dict syntax and incorrect
duplicate assertions: fix the data_dict declaration in test_json_dict_to_model
(replace the invalid 'agentname": "test-agent",' with a proper "name":
"test-agent", ensure agent_id is set or referenced correctly), remove the
duplicated call/assert block, and replace the invalid AgentType.GENERALIST
assertion with the correct expected enum (e.g., AgentType.CLI) when asserting
metadata.type; confirm the test uses json_dict_to_model and AgentMetadata
references consistently.
| def test_model_serializer_class(self): | ||
| """Test ModelSerializer class.""" | ||
| seriname="test-agent", | ||
| type=AgentType.CLI, | ||
| status=AgentStatus.ACTIVE, | ||
| version="0.3.0", | ||
| ) | ||
|
|
||
| # Serialize | ||
| json_str = serializer.serialize(metadata) | ||
| assert isinstance(json_str, str) | ||
|
|
||
| # Deserialize | ||
| restored = serializer.deserialize(json_str) | ||
| assert restored.id == original_id | ||
| assert restored.name == "test-agent" | ||
| assert restored.type == AgentType.CLI | ||
|
|
||
| # Deserialize | ||
| restored = serializer.deserialize(json_str) | ||
| assert restored.id == original_id | ||
| assert restored.type == AgentType.SPECIALIST |
There was a problem hiding this comment.
Critical syntax errors in test_model_serializer_class.
Line 295 has malformed variable declaration (seriname=), and lines 311-314 contain duplicate code with reference to non-existent AgentType.SPECIALIST.
🐛 Proposed fix
def test_model_serializer_class(self):
"""Test ModelSerializer class."""
- seriname="test-agent",
+ serializer = ModelSerializer(AgentMetadata)
+ original_id = uuid4()
+ metadata = AgentMetadata(
+ id=original_id,
+ name="test-agent",
type=AgentType.CLI,
status=AgentStatus.ACTIVE,
version="0.3.0",
)
# Serialize
json_str = serializer.serialize(metadata)
assert isinstance(json_str, str)
# Deserialize
restored = serializer.deserialize(json_str)
assert restored.id == original_id
assert restored.name == "test-agent"
assert restored.type == AgentType.CLI
-
- # Deserialize
- restored = serializer.deserialize(json_str)
- assert restored.id == original_id
- assert restored.type == AgentType.SPECIALIST🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_agent_sdk_foundation.py` around lines 293 - 314, In
test_model_serializer_class fix the malformed variable declaration and the
duplicated/incorrect assertions: replace the broken "seriname=" declaration with
a proper creation of the metadata object used by serializer (populate
name="test-agent", type=AgentType.CLI, status=AgentStatus.ACTIVE,
version="0.3.0" and ensure original_id is set from that object), remove the
second duplicate deserialize block, and update assertions so they consistently
check restored.id == original_id, restored.name == "test-agent", and
restored.type == AgentType.CLI (remove any reference to the non-existent
AgentType.SPECIALIST).
| def test_full_workflow_create_metadata_serialize_deserialize(self): | ||
| """Test complete workflow: create metadata, serialize, deserialize.""" | ||
| agent_id = uuid4() | ||
|
|
||
| # 1. Create metadata | ||
| metadata = AgentMetadata( | ||
| name="test-agent", | ||
| type=AgentType.CLI, | ||
| status=AgentStatus.ACTIVE, | ||
| version="0.3.0", | ||
| ) | ||
|
|
||
| # 2. Serialize to JSON | ||
| json_str = serialize_to_json(metadata) | ||
| assert isinstance(json_str, str) | ||
|
|
||
| # 3. Deserialize back | ||
| restored = deserialize_from_json(json_str, AgentMetadata) | ||
|
|
||
| # 4. Verify | ||
| assert restored.id == agent_id | ||
| assert restored.name == "test-agent" | ||
| assert restored.version == "0.3.0" | ||
|
|
||
| def test_full_agent_identity_workflow(self): | ||
| """Test creating complete agent identity and serializing.""" | ||
| agent_id = uuid4() | ||
|
|
||
| identity = AgentIdentity( | ||
| id=agent_id, | ||
| personality=AgentPersonality( | ||
| name="ArchitectureExpert", | ||
| style="direct", | ||
| communication="Clear and concise", | ||
| ), | ||
| goals=AgentGoals( | ||
| primary=["Improve code architecture"], | ||
| secondary=["Reduce cyclomatic complexity"], | ||
| ), | ||
| capabilities=AgentCapabilities( | ||
| strengths=["Python", "Go", "Architecture"], | ||
| weaknesses=["DevOps"], | ||
| ), | ||
| constraints=AgentConstraints( | ||
| hard_rules=["Never delete code without tests"], | ||
| soft_rules=["Keep functions under 50 lines"], | ||
| ), | ||
| ) | ||
|
|
||
| # Serialize | ||
| json_str = identity.model_dump_json() | ||
| assert isinstance(json_str, str) | ||
| # Deserialize | ||
| assert restored["id"] == str(agent_id) | ||
| assert restored["personality"]["name"] == "ArchitectureExpert" | ||
| assert "Python" in restored["capabilities"]["strength | ||
| assert "Python" in restored["capabilities"]["supported_languages"] |
There was a problem hiding this comment.
Integration test has assertion errors and malformed code.
- Line 390: Asserts
restored.id == agent_idbutagent_idis created at line 372 and never passed toAgentMetadata(line 375 creates it withoutid=agent_id) - Lines 423-426: Truncated assertion strings with syntax errors
🐛 Proposed fix for first integration test
def test_full_workflow_create_metadata_serialize_deserialize(self):
"""Test complete workflow: create metadata, serialize, deserialize."""
agent_id = uuid4()
# 1. Create metadata
metadata = AgentMetadata(
+ id=agent_id,
name="test-agent",
type=AgentType.CLI,
status=AgentStatus.ACTIVE,
version="0.3.0",
)🐛 Proposed fix for second integration test
# Serialize
json_str = identity.model_dump_json()
assert isinstance(json_str, str)
# Deserialize
- assert restored["id"] == str(agent_id)
- assert restored["personality"]["name"] == "ArchitectureExpert"
- assert "Python" in restored["capabilities"]["strength
- assert "Python" in restored["capabilities"]["supported_languages"]
+ restored = json.loads(json_str)
+ assert restored["id"] == str(agent_id)
+ assert restored["personality"]["name"] == "ArchitectureExpert"
+ assert "Python" in restored["capabilities"]["strengths"]📝 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.
| def test_full_workflow_create_metadata_serialize_deserialize(self): | |
| """Test complete workflow: create metadata, serialize, deserialize.""" | |
| agent_id = uuid4() | |
| # 1. Create metadata | |
| metadata = AgentMetadata( | |
| name="test-agent", | |
| type=AgentType.CLI, | |
| status=AgentStatus.ACTIVE, | |
| version="0.3.0", | |
| ) | |
| # 2. Serialize to JSON | |
| json_str = serialize_to_json(metadata) | |
| assert isinstance(json_str, str) | |
| # 3. Deserialize back | |
| restored = deserialize_from_json(json_str, AgentMetadata) | |
| # 4. Verify | |
| assert restored.id == agent_id | |
| assert restored.name == "test-agent" | |
| assert restored.version == "0.3.0" | |
| def test_full_agent_identity_workflow(self): | |
| """Test creating complete agent identity and serializing.""" | |
| agent_id = uuid4() | |
| identity = AgentIdentity( | |
| id=agent_id, | |
| personality=AgentPersonality( | |
| name="ArchitectureExpert", | |
| style="direct", | |
| communication="Clear and concise", | |
| ), | |
| goals=AgentGoals( | |
| primary=["Improve code architecture"], | |
| secondary=["Reduce cyclomatic complexity"], | |
| ), | |
| capabilities=AgentCapabilities( | |
| strengths=["Python", "Go", "Architecture"], | |
| weaknesses=["DevOps"], | |
| ), | |
| constraints=AgentConstraints( | |
| hard_rules=["Never delete code without tests"], | |
| soft_rules=["Keep functions under 50 lines"], | |
| ), | |
| ) | |
| # Serialize | |
| json_str = identity.model_dump_json() | |
| assert isinstance(json_str, str) | |
| # Deserialize | |
| assert restored["id"] == str(agent_id) | |
| assert restored["personality"]["name"] == "ArchitectureExpert" | |
| assert "Python" in restored["capabilities"]["strength | |
| assert "Python" in restored["capabilities"]["supported_languages"] | |
| def test_full_workflow_create_metadata_serialize_deserialize(self): | |
| """Test complete workflow: create metadata, serialize, deserialize.""" | |
| agent_id = uuid4() | |
| # 1. Create metadata | |
| metadata = AgentMetadata( | |
| id=agent_id, | |
| name="test-agent", | |
| type=AgentType.CLI, | |
| status=AgentStatus.ACTIVE, | |
| version="0.3.0", | |
| ) | |
| # 2. Serialize to JSON | |
| json_str = serialize_to_json(metadata) | |
| assert isinstance(json_str, str) | |
| # 3. Deserialize back | |
| restored = deserialize_from_json(json_str, AgentMetadata) | |
| # 4. Verify | |
| assert restored.id == agent_id | |
| assert restored.name == "test-agent" | |
| assert restored.version == "0.3.0" |
| def test_full_workflow_create_metadata_serialize_deserialize(self): | |
| """Test complete workflow: create metadata, serialize, deserialize.""" | |
| agent_id = uuid4() | |
| # 1. Create metadata | |
| metadata = AgentMetadata( | |
| name="test-agent", | |
| type=AgentType.CLI, | |
| status=AgentStatus.ACTIVE, | |
| version="0.3.0", | |
| ) | |
| # 2. Serialize to JSON | |
| json_str = serialize_to_json(metadata) | |
| assert isinstance(json_str, str) | |
| # 3. Deserialize back | |
| restored = deserialize_from_json(json_str, AgentMetadata) | |
| # 4. Verify | |
| assert restored.id == agent_id | |
| assert restored.name == "test-agent" | |
| assert restored.version == "0.3.0" | |
| def test_full_agent_identity_workflow(self): | |
| """Test creating complete agent identity and serializing.""" | |
| agent_id = uuid4() | |
| identity = AgentIdentity( | |
| id=agent_id, | |
| personality=AgentPersonality( | |
| name="ArchitectureExpert", | |
| style="direct", | |
| communication="Clear and concise", | |
| ), | |
| goals=AgentGoals( | |
| primary=["Improve code architecture"], | |
| secondary=["Reduce cyclomatic complexity"], | |
| ), | |
| capabilities=AgentCapabilities( | |
| strengths=["Python", "Go", "Architecture"], | |
| weaknesses=["DevOps"], | |
| ), | |
| constraints=AgentConstraints( | |
| hard_rules=["Never delete code without tests"], | |
| soft_rules=["Keep functions under 50 lines"], | |
| ), | |
| ) | |
| # Serialize | |
| json_str = identity.model_dump_json() | |
| assert isinstance(json_str, str) | |
| # Deserialize | |
| assert restored["id"] == str(agent_id) | |
| assert restored["personality"]["name"] == "ArchitectureExpert" | |
| assert "Python" in restored["capabilities"]["strength | |
| assert "Python" in restored["capabilities"]["supported_languages"] | |
| def test_full_agent_identity_workflow(self): | |
| """Test creating complete agent identity and serializing.""" | |
| agent_id = uuid4() | |
| identity = AgentIdentity( | |
| id=agent_id, | |
| personality=AgentPersonality( | |
| name="ArchitectureExpert", | |
| style="direct", | |
| communication="Clear and concise", | |
| ), | |
| goals=AgentGoals( | |
| primary=["Improve code architecture"], | |
| secondary=["Reduce cyclomatic complexity"], | |
| ), | |
| capabilities=AgentCapabilities( | |
| strengths=["Python", "Go", "Architecture"], | |
| weaknesses=["DevOps"], | |
| ), | |
| constraints=AgentConstraints( | |
| hard_rules=["Never delete code without tests"], | |
| soft_rules=["Keep functions under 50 lines"], | |
| ), | |
| ) | |
| # Serialize | |
| json_str = identity.model_dump_json() | |
| assert isinstance(json_str, str) | |
| # Deserialize | |
| restored = json.loads(json_str) | |
| assert restored["id"] == str(agent_id) | |
| assert restored["personality"]["name"] == "ArchitectureExpert" | |
| assert "Python" in restored["capabilities"]["strengths"] |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_agent_sdk_foundation.py` around lines 370 - 426, In
test_full_workflow_create_metadata_serialize_deserialize: ensure the created
AgentMetadata includes the generated agent_id (pass id=agent_id into
AgentMetadata) or change the assertion to expect whatever ID was actually
created; then serialize with serialize_to_json and deserialize with
deserialize_from_json and assert restored.id matches the agent_id you supplied.
In test_full_agent_identity_workflow: actually parse the JSON produced by
identity.model_dump_json() into restored (e.g., json.loads or
deserialize_from_json) before accessing keys, then fix the broken assertions to
use complete keys and existing fields — assert restored["id"] == str(agent_id),
restored["personality"]["name"] == "ArchitectureExpert", and assert "Python" in
restored["capabilities"]["strengths"] (remove or replace the invalid check for
supported_languages). Ensure variable names AgentIdentity, model_dump_json,
restored, and agent_id are used consistently.
There was a problem hiding this comment.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (5)
src/ghostclaw/core/agent_sdk/agent_session.py-421-443 (1)
421-443:⚠️ Potential issue | 🟡 MinorISO format timestamps lack 'Z' suffix for UTC indication.
The
isoformat()calls don't append the 'Z' suffix required by coding guidelines. Once timestamps are stored in UTC, serialize them with the timezone suffix.Proposed fix
return { "session_id": str(self._session_id) if self._session_id else None, "agent_id": str(self.agent_id), "state": self._state.value, - "created_at": self._created_at.isoformat(), - "started_at": self._started_at.isoformat() if self._started_at else None, - "ended_at": self._ended_at.isoformat() if self._ended_at else None, + "created_at": self._created_at.isoformat().replace("+00:00", "Z"), + "started_at": self._started_at.isoformat().replace("+00:00", "Z") if self._started_at else None, + "ended_at": self._ended_at.isoformat().replace("+00:00", "Z") if self._ended_at else None,Alternatively, define a helper:
def to_iso_z(dt): return dt.strftime("%Y-%m-%dT%H:%M:%SZ")for cleaner formatting.As per coding guidelines: "Use UTC ISO format with 'Z' suffix for timestamps".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_session.py` around lines 421 - 443, The serialized timestamps in the session dict (self._created_at, self._started_at, self._ended_at and each action's a.timestamp in the list comprehension inside agent_session.py) use datetime.isoformat() which does not append the 'Z' UTC suffix; update serialization to emit UTC ISO strings with a trailing 'Z' (e.g., use a helper like to_iso_z(dt) that formats via dt.strftime("%Y-%m-%dT%H:%M:%SZ") or dt.replace(tzinfo=timezone.utc).isoformat().replace("+00:00","Z")) and apply it to "created_at", "started_at", "ended_at" and action "timestamp" while leaving None handling unchanged.src/ghostclaw/core/agent_sdk/agent_cli.py-604-621 (1)
604-621:⚠️ Potential issue | 🟡 Minor
_workspace_listshould use the workspace manager'slist_filesmethod instead of directly accessing project files.The method lists files from
current_session["project_path"]but is named_workspace_list, suggesting it should list workspace sandbox files. TheAgentWorkspaceManageralready provides alist_files()method that operates on itsworkspace_rootwith proper filtering and file metadata. Useworkspace_mgr.list_files(pattern=args[0] if args else "*")for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 604 - 621, The _workspace_list function is incorrectly reading files from current_session["project_path"] instead of using the workspace manager; replace the direct Path.glob logic with a call to workspace_mgr.list_files(pattern=args[0] if args else "*"), then build and return the CommandResult from the returned list (convert entries to strings or use provided metadata) and preserve error handling; ensure you remove direct usage of Path(self.current_session["project_path"]) and reference workspace_mgr.list_files and CommandResult when making the change.tests/unit/test_agent_telemetry.py-312-313 (1)
312-313:⚠️ Potential issue | 🟡 MinorOpen the fixture file with explicit UTF-8.
This test currently relies on the platform default encoding.
As per coding guidelines, "Always use utf-8 encoding when opening files".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_agent_telemetry.py` around lines 312 - 313, The test opens the events file with the platform default encoding; update the file open call that uses telemetry_manager.events_file in the context manager (the with open(...): block that reads lines into the variable lines) to explicitly specify encoding="utf-8" so the fixture is always read using UTF-8.ORCHESTRATOR_TESTING_SUMMARY.md-175-176 (1)
175-176:⚠️ Potential issue | 🟡 MinorThis example resolves an empty plan.
create_plan()is called withouttask_ids, soresolve_execution_order(plan_id)returns[]with the current implementation.Suggested fix
-plan_id = orch.create_plan("Full Analysis", "Complete analysis workflow") +plan_id = orch.create_plan( + "Full Analysis", + "Complete analysis workflow", + task_ids=[analyze_id, report_id], +)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ORCHESTRATOR_TESTING_SUMMARY.md` around lines 175 - 176, The example calls orch.create_plan("Full Analysis", "Complete analysis workflow") with no task_ids so resolve_execution_order(plan_id) returns an empty list; update the example to create or reference tasks and pass their IDs into create_plan (or create tasks via orch.add_task / orch.register_task and then include those task IDs in the create_plan call) so that resolve_execution_order(plan_id) returns the expected execution order for that plan_id.tests/unit/test_agent_telemetry.py-348-350 (1)
348-350:⚠️ Potential issue | 🟡 MinorThis assertion doesn't prove rotation happened.
events.jsonlalready makeslen(telemetry_files) >= 1true, so the test still passes if_rotate_events_file()never runs.Suggested fix
- telemetry_files = list(telemetry_manager.telemetry_dir.glob("events*.jsonl")) - assert len(telemetry_files) >= 1 + rotated_files = list(telemetry_manager.telemetry_dir.glob("events_*.jsonl")) + assert rotated_files + assert telemetry_manager.events_file.exists()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/test_agent_telemetry.py` around lines 348 - 350, The test currently asserts len(list(telemetry_manager.telemetry_dir.glob("events*.jsonl"))) >= 1 which is satisfied by the original events.jsonl and doesn’t prove _rotate_events_file() ran; update the test to record the telemetry file list (or count) before calling telemetry_manager._rotate_events_file(), invoke telemetry_manager._rotate_events_file(), then assert the file count increased or that a new filename matching the rotated pattern (e.g., not exactly "events.jsonl" but "events*.jsonl" excluding the original or a suffix like ".1" / timestamp) exists in telemetry_manager.telemetry_dir to prove rotation occurred. Ensure you reference telemetry_manager.telemetry_dir and _rotate_events_file() when locating the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/TASK_ORCHESTRATOR.md`:
- Around line 16-18: The API docs are out of sync with the shipped orchestrator:
update the documented enums and signatures to match AgentTaskOrchestrator's
current surface by (1) removing references to numeric priority values and
listing only the actual priority enum names used by AgentTaskOrchestrator, (2)
replacing old TaskType names with the current TaskType enum members, (3)
changing TaskPlan.tasks documentation to indicate it contains Task objects (or
the actual type returned) instead of UUIDs, (4) removing filter arguments from
the get_all_tasks() signature and documenting its real parameters/return value,
and (5) correct start_task() behavior to state that paused tasks cannot be
started if the orchestrator disallows it; reference AgentTaskOrchestrator,
TaskType, TaskPlan.tasks, get_all_tasks(), and start_task() when making these
edits so the docs match runtime behavior.
In `@src/ghostclaw/core/agent_sdk/agent_cli.py`:
- Around line 356-387: The memory subcommand handler assumes managers exist even
when a session is only created but not started; update the block that calls
self.session_manager.get_memory_manager() (in the method handling memory
subcommands) to detect a None memory_mgr and return a clear CommandResult error
guiding the user to start the session (or call
_initialize_managers/start_session) instead of proceeding, so subsequent calls
to _memory_add/_memory_list/_memory_search/_memory_stats/_memory_export never
receive a None manager.
- Around line 470-503: The workspace subcommands call
self.session_manager.get_workspace_manager() which can return None if the
session exists but hasn't been started, causing AttributeError for
_workspace_status, _workspace_branch, _workspace_commit, _workspace_history, and
_workspace_list; update the dispatch in the method that handles subcommand
routing to check if workspace_mgr is None (after calling
get_workspace_manager()) and if so allow only the "init" subcommand to proceed
(delegate to _workspace_init) and return a clear CommandResult failure for other
subcommands instructing to start the session first (or call start_session) —
reference get_workspace_manager, _workspace_init, _workspace_status,
_workspace_branch, _workspace_commit, _workspace_history, and _workspace_list
when making the change.
- Around line 43-54: AgentCLI.__init__ currently accepts agent_id: str (default
"default-agent") but downstream classes (AgentSessionManager,
AgentIdentityManager, AgentMemoryManager, AgentWorkspaceManager) expect a UUID;
convert or enforce a UUID here to avoid ValueError. Update AgentCLI.__init__ to
accept/require a uuid.UUID (or convert incoming string by calling
uuid.UUID(agent_id) or creating one when absent, e.g., uuid.uuid4()), validate
the conversion and pass the uuid.UUID instance into
AgentSessionManager(agent_id=...) and any other managers; ensure the default is
a UUID rather than the literal string and raise a clear error if a provided
string is not a valid UUID.
In `@src/ghostclaw/core/agent_sdk/agent_session.py`:
- Around line 125-130: Timestamps are being created with local time
(datetime.now()) across Session initialization and lifecycle methods; change
every datetime.now() usage in agent_session.py (e.g., in __init__,
create_session, start/ pause/ end methods and any duration calculations) to
datetime.now(timezone.utc) so all stored datetimes are UTC, and when emitting
ISO strings ensure you format them as UTC with the 'Z' suffix (e.g., use
.isoformat() converted to replace +00:00 with Z or otherwise emit UTC ISO 'Z').
Update places referenced by the review such as the attributes _created_at,
_started_at, _paused_at, _ended_at, the create_session method and any duration
calculations to use timezone-aware UTC timestamps.
- Line 44: The SessionAction model's timestamp Field currently uses datetime.now
(local time); change its default_factory to return a UTC-aware datetime (e.g.,
datetime.now(timezone.utc) or datetime.utcnow().replace(tzinfo=timezone.utc)) so
timestamps are in UTC and will serialize with the 'Z' suffix per the guidelines,
and add the necessary import for timezone from datetime; update the timestamp
Field definition in SessionAction accordingly.
In `@src/ghostclaw/core/agent_sdk/agent_task_orchestrator.py`:
- Around line 363-399: The DFS visit function (visit(task_id)) can return False
on cycle detection but the outer loop ignores that and leaves
plan.execution_order partially populated; fix by making the topological sort
atomic: run visit for each task in sorted_tasks and if any visit(task.id)
returns False (or a cycle is detected), immediately raise a clear exception
(e.g., CycleDetectedError/ValueError) and do not assign or publish the partially
built execution_order, and only set plan.execution_order (or append to it) after
the full traversal succeeds. Ensure references to visit, get_task,
execution_order, sorted_tasks and plan.execution_order are used so the check and
exception are implemented at the loop level that currently calls visit(task.id).
- Line 427: The metrics are counting attempts not unique tasks because
_execution_queue is never drained and _completed_queue is appended on every
failure/retry; update the logic to treat these as sets of unique task IDs
instead of append-only lists: change the code paths that append to
_completed_queue (where failures/retries/success are handled) to first check
membership and only add the task_id once (or use a set) and ensure that when a
task finishes successfully or is finally failed you remove its id from
_execution_queue (or maintain _execution_set) so execution_queue_size reflects
active tasks; locate and fix the append/remove logic around _execution_queue and
_completed_queue (where self._execution_queue.append(task_id) and similar
appends occur) so retries don’t double-count and completed_tasks is unique.
- Around line 538-579: get_plan_progress currently counts only fully completed
tasks and returns completed_count / total, which ignores per-task partial
progress values; update get_plan_progress to sum per-task contribution using a
task.progress (or 1.0 if task.is_completed()) fallback to 0.0 when missing, i.e.
total_progress = sum(task.progress if hasattr(task, "progress") else (1.0 if
task.is_completed() else 0.0)) and return total_progress / len(plan.tasks). Also
update get_plan_status_summary to include TaskState.PAUSED and TaskState.QUEUED
in the summary dict and increment those counters when encountered so totals
remain consistent with pause_task()/queueing logic. Ensure you reference
get_plan_progress, get_plan_status_summary, TaskState, and
task.progress/task.is_completed() when making changes.
- Around line 63-64: The model fields and state transitions currently use naive
local datetimes (e.g., the created_at Field default_factory=datetime.now) which
must be replaced with UTC-aware timestamps; change Field default_factories
(created_at, started_at, updated_at, completed_at, etc.) to produce
timezone-aware UTC datetimes (e.g., datetime.now(timezone.utc) or
datetime.utcnow().replace(tzinfo=timezone.utc)) and update all places in
AgentTaskOrchestrator methods that set timestamps (state transition code around
lines referenced) to set timezone-aware UTC values consistently so serialized
ISO strings include the 'Z' suffix.
- Around line 115-117: The is_completed method currently treats FAILED and
CANCELLED as completed; change is_completed(self) to return only
TaskState.COMPLETED (i.e., return self.state == TaskState.COMPLETED) and add a
new helper like is_terminal(self) (or is_final) that returns True for
TaskState.COMPLETED, TaskState.FAILED, and TaskState.CANCELLED; then update call
sites that actually need any terminal state to use the new is_terminal helper
while leaving callers that truly want "completed" unchanged.
- Around line 417-423: The current start_task flow sets task.state =
TaskState.BLOCKED when dependencies are unmet but then immediately rejects
non-pending tasks, so a previously blocked task can never be started once
dependencies are fixed; modify start_task (referencing get_task, is_pending,
are_dependencies_met, TaskState.BLOCKED) so that BLOCKED tasks are allowed to
proceed when are_dependencies_met(task_id) returns True: e.g., change the
initial guard to only return False for non-startable terminal states (not
pending AND not BLOCKED), and if dependencies are now met and task.state ==
TaskState.BLOCKED, move it back to a runnable state (e.g., set to pending or
continue to start) before proceeding with the start logic.
In `@src/ghostclaw/core/agent_sdk/agent_telemetry.py`:
- Around line 388-395: load_events currently only reads the live events.jsonl so
after _rotate_events_file renames older segments to events_*.jsonl you lose
history; update load_events to also glob telemetry_dir for rotated files (e.g.,
matching "events_*.jsonl" and "events.jsonl"), open and read them all in
chronological order (sort by filename/timestamp) before returning combined
events, and ensure this logic references telemetry_dir, _rotate_events_file and
events_file so rotated segments are included in the telemetry load.
- Around line 355-360: Replace raw logger.error calls in the adapter error
handlers with structured ArchitectureReport usage: when calling
self.adapter.flush(), self.adapter.write(...), and self.adapter.load(...) catch
exceptions and create an ArchitectureReport (including a clear operation id like
"telemetry.adapter.flush"/"telemetry.adapter.write"/"telemetry.adapter.load",
the exception message, stacktrace, and any relevant context such as agent id or
payload size), then emit or return that ArchitectureReport instead of a plain
log string so callers can inspect and propagate failures; update the three
handler blocks (the flush block around self.adapter.flush(), the write block
around adapter.write, and the load block) to construct and forward the
ArchitectureReport rather than only calling logger.error.
- Around line 75-95: The timestamp field uses datetime.now() and to_dict calls
.isoformat(), producing naive local timestamps; change the timestamp Field
default_factory to produce a UTC-aware datetime (e.g., use datetime.utcnow()
with timezone.utc or datetime.now(timezone.utc)) and ensure to_dict emits UTC
ISO with a trailing 'Z' (e.g., format or replace +00:00 with 'Z') so the JSONL
stream contains explicit UTC timestamps; update the timestamp Field declaration
and the to_dict serialization in agent_telemetry.py (affecting the timestamp
Field and the to_dict method) and apply the same pattern to the other occurrence
noted around the second timestamp usage.
In `@tests/integration/test_agent_sdk_integration.py`:
- Around line 38-48: The test patches
ghostclaw.core.agent_sdk.config.get_settings but AgentTelemetryManager imports
get_settings into its own module namespace (from .config import get_settings),
so the fixture must patch ghostclaw.core.agent_sdk.agent_telemetry.get_settings
instead; update the test's patch target to patch
"ghostclaw.core.agent_sdk.agent_telemetry.get_settings" (so
AgentTelemetryManager.__init__ uses the mocked settings), keep returning a
MagicMock with memory_base_dir and ensure telemetry_dir setup as before to avoid
the real telemetry directory creation.
---
Minor comments:
In `@ORCHESTRATOR_TESTING_SUMMARY.md`:
- Around line 175-176: The example calls orch.create_plan("Full Analysis",
"Complete analysis workflow") with no task_ids so
resolve_execution_order(plan_id) returns an empty list; update the example to
create or reference tasks and pass their IDs into create_plan (or create tasks
via orch.add_task / orch.register_task and then include those task IDs in the
create_plan call) so that resolve_execution_order(plan_id) returns the expected
execution order for that plan_id.
In `@src/ghostclaw/core/agent_sdk/agent_cli.py`:
- Around line 604-621: The _workspace_list function is incorrectly reading files
from current_session["project_path"] instead of using the workspace manager;
replace the direct Path.glob logic with a call to
workspace_mgr.list_files(pattern=args[0] if args else "*"), then build and
return the CommandResult from the returned list (convert entries to strings or
use provided metadata) and preserve error handling; ensure you remove direct
usage of Path(self.current_session["project_path"]) and reference
workspace_mgr.list_files and CommandResult when making the change.
In `@src/ghostclaw/core/agent_sdk/agent_session.py`:
- Around line 421-443: The serialized timestamps in the session dict
(self._created_at, self._started_at, self._ended_at and each action's
a.timestamp in the list comprehension inside agent_session.py) use
datetime.isoformat() which does not append the 'Z' UTC suffix; update
serialization to emit UTC ISO strings with a trailing 'Z' (e.g., use a helper
like to_iso_z(dt) that formats via dt.strftime("%Y-%m-%dT%H:%M:%SZ") or
dt.replace(tzinfo=timezone.utc).isoformat().replace("+00:00","Z")) and apply it
to "created_at", "started_at", "ended_at" and action "timestamp" while leaving
None handling unchanged.
In `@tests/unit/test_agent_telemetry.py`:
- Around line 312-313: The test opens the events file with the platform default
encoding; update the file open call that uses telemetry_manager.events_file in
the context manager (the with open(...): block that reads lines into the
variable lines) to explicitly specify encoding="utf-8" so the fixture is always
read using UTF-8.
- Around line 348-350: The test currently asserts
len(list(telemetry_manager.telemetry_dir.glob("events*.jsonl"))) >= 1 which is
satisfied by the original events.jsonl and doesn’t prove _rotate_events_file()
ran; update the test to record the telemetry file list (or count) before calling
telemetry_manager._rotate_events_file(), invoke
telemetry_manager._rotate_events_file(), then assert the file count increased or
that a new filename matching the rotated pattern (e.g., not exactly
"events.jsonl" but "events*.jsonl" excluding the original or a suffix like ".1"
/ timestamp) exists in telemetry_manager.telemetry_dir to prove rotation
occurred. Ensure you reference telemetry_manager.telemetry_dir and
_rotate_events_file() when locating the code to change.
🪄 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: ae555d15-d1b7-4899-a71e-5758695ecbf3
📒 Files selected for processing (12)
AGENT_CAPABILITIES_ARCHITECTURE.mdORCHESTRATOR_TESTING_SUMMARY.mddocs/TASK_ORCHESTRATOR.mdsrc/ghostclaw/core/agent_sdk/__init__.pysrc/ghostclaw/core/agent_sdk/agent_cli.pysrc/ghostclaw/core/agent_sdk/agent_memory.pysrc/ghostclaw/core/agent_sdk/agent_session.pysrc/ghostclaw/core/agent_sdk/agent_task_orchestrator.pysrc/ghostclaw/core/agent_sdk/agent_telemetry.pytests/integration/test_agent_sdk_integration.pytests/unit/test_agent_task_orchestrator.pytests/unit/test_agent_telemetry.py
✅ Files skipped from review due to trivial changes (1)
- AGENT_CAPABILITIES_ARCHITECTURE.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/ghostclaw/core/agent_sdk/init.py
- src/ghostclaw/core/agent_sdk/agent_memory.py
| - `state`: Current state (PENDING, RUNNING, COMPLETED, FAILED, PAUSED, CANCELLED, BLOCKED) | ||
| - `priority`: Priority level (LOW, NORMAL, HIGH, CRITICAL) | ||
| - `type`: Task type (ANALYSIS, IMPLEMENTATION, TESTING, DEBUGGING, VALIDATION, OTHER) |
There was a problem hiding this comment.
This API reference is out of sync with the shipped orchestrator.
This page still documents enum members and signatures the current AgentTaskOrchestrator does not expose: numeric priorities, old TaskType names, TaskPlan.tasks as UUIDs, filter args on get_all_tasks(), and paused tasks being startable via start_task(). Consumers following this reference will hit runtime mismatches immediately.
Also applies to: 52-55, 61-67, 72-83, 115-123, 205-212
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/TASK_ORCHESTRATOR.md` around lines 16 - 18, The API docs are out of sync
with the shipped orchestrator: update the documented enums and signatures to
match AgentTaskOrchestrator's current surface by (1) removing references to
numeric priority values and listing only the actual priority enum names used by
AgentTaskOrchestrator, (2) replacing old TaskType names with the current
TaskType enum members, (3) changing TaskPlan.tasks documentation to indicate it
contains Task objects (or the actual type returned) instead of UUIDs, (4)
removing filter arguments from the get_all_tasks() signature and documenting its
real parameters/return value, and (5) correct start_task() behavior to state
that paused tasks cannot be started if the orchestrator disallows it; reference
AgentTaskOrchestrator, TaskType, TaskPlan.tasks, get_all_tasks(), and
start_task() when making these edits so the docs match runtime behavior.
| if not self.current_session: | ||
| return CommandResult( | ||
| success=False, message="No active session. Create one first." | ||
| ) | ||
|
|
||
| subcommand = args[0].lower() | ||
| subargs = args[1:] | ||
|
|
||
| try: | ||
| memory_mgr = self.session_manager.get_memory_manager() | ||
|
|
||
| if subcommand == "add": | ||
| return self._memory_add(memory_mgr, subargs) | ||
| elif subcommand == "list": | ||
| return self._memory_list(memory_mgr, subargs) | ||
| elif subcommand == "search": | ||
| return self._memory_search(memory_mgr, subargs) | ||
| elif subcommand == "stats": | ||
| return self._memory_stats(memory_mgr, subargs) | ||
| elif subcommand == "export": | ||
| return self._memory_export(memory_mgr, subargs) | ||
| else: | ||
| return CommandResult( | ||
| success=False, | ||
| message=f"Unknown memory subcommand: {subcommand}", | ||
| ) | ||
| except Exception as e: | ||
| return CommandResult( | ||
| success=False, | ||
| message=f"Memory error: {str(e)}", | ||
| error=str(e), | ||
| ) |
There was a problem hiding this comment.
Memory commands fail if session is created but not started.
The check on line 356 verifies current_session exists, but managers are only initialized when start_session() is called (via _initialize_managers). If a user creates a session but doesn't start it, get_memory_manager() returns None, and calling methods on it raises AttributeError.
Proposed fix
def _handle_memory(self, args: list) -> CommandResult:
"""Handle memory commands."""
if not args:
return CommandResult(
success=False,
message="Memory subcommand required. Use 'help memory' for options.",
)
if not self.current_session:
return CommandResult(
success=False, message="No active session. Create one first."
)
+ memory_mgr = self.session_manager.get_memory_manager()
+ if memory_mgr is None:
+ return CommandResult(
+ success=False, message="Session not started. Run 'session start' first."
+ )
+
subcommand = args[0].lower()
subargs = args[1:]
try:
- memory_mgr = self.session_manager.get_memory_manager()📝 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.
| if not self.current_session: | |
| return CommandResult( | |
| success=False, message="No active session. Create one first." | |
| ) | |
| subcommand = args[0].lower() | |
| subargs = args[1:] | |
| try: | |
| memory_mgr = self.session_manager.get_memory_manager() | |
| if subcommand == "add": | |
| return self._memory_add(memory_mgr, subargs) | |
| elif subcommand == "list": | |
| return self._memory_list(memory_mgr, subargs) | |
| elif subcommand == "search": | |
| return self._memory_search(memory_mgr, subargs) | |
| elif subcommand == "stats": | |
| return self._memory_stats(memory_mgr, subargs) | |
| elif subcommand == "export": | |
| return self._memory_export(memory_mgr, subargs) | |
| else: | |
| return CommandResult( | |
| success=False, | |
| message=f"Unknown memory subcommand: {subcommand}", | |
| ) | |
| except Exception as e: | |
| return CommandResult( | |
| success=False, | |
| message=f"Memory error: {str(e)}", | |
| error=str(e), | |
| ) | |
| if not self.current_session: | |
| return CommandResult( | |
| success=False, message="No active session. Create one first." | |
| ) | |
| memory_mgr = self.session_manager.get_memory_manager() | |
| if memory_mgr is None: | |
| return CommandResult( | |
| success=False, message="Session not started. Run 'session start' first." | |
| ) | |
| subcommand = args[0].lower() | |
| subargs = args[1:] | |
| try: | |
| if subcommand == "add": | |
| return self._memory_add(memory_mgr, subargs) | |
| elif subcommand == "list": | |
| return self._memory_list(memory_mgr, subargs) | |
| elif subcommand == "search": | |
| return self._memory_search(memory_mgr, subargs) | |
| elif subcommand == "stats": | |
| return self._memory_stats(memory_mgr, subargs) | |
| elif subcommand == "export": | |
| return self._memory_export(memory_mgr, subargs) | |
| else: | |
| return CommandResult( | |
| success=False, | |
| message=f"Unknown memory subcommand: {subcommand}", | |
| ) | |
| except Exception as e: | |
| return CommandResult( | |
| success=False, | |
| message=f"Memory error: {str(e)}", | |
| error=str(e), | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 356 - 387, The memory
subcommand handler assumes managers exist even when a session is only created
but not started; update the block that calls
self.session_manager.get_memory_manager() (in the method handling memory
subcommands) to detect a None memory_mgr and return a clear CommandResult error
guiding the user to start the session (or call
_initialize_managers/start_session) instead of proceeding, so subsequent calls
to _memory_add/_memory_list/_memory_search/_memory_stats/_memory_export never
receive a None manager.
| if not self.current_session: | ||
| return CommandResult( | ||
| success=False, message="No active session. Create one first." | ||
| ) | ||
|
|
||
| subcommand = args[0].lower() | ||
| subargs = args[1:] | ||
|
|
||
| try: | ||
| workspace_mgr = self.session_manager.get_workspace_manager() | ||
|
|
||
| if subcommand == "init": | ||
| return self._workspace_init(workspace_mgr, subargs) | ||
| elif subcommand == "status": | ||
| return self._workspace_status(workspace_mgr, subargs) | ||
| elif subcommand == "branch": | ||
| return self._workspace_branch(workspace_mgr, subargs) | ||
| elif subcommand == "commit": | ||
| return self._workspace_commit(workspace_mgr, subargs) | ||
| elif subcommand == "history": | ||
| return self._workspace_history(workspace_mgr, subargs) | ||
| elif subcommand == "list": | ||
| return self._workspace_list(workspace_mgr, subargs) | ||
| else: | ||
| return CommandResult( | ||
| success=False, | ||
| message=f"Unknown workspace subcommand: {subcommand}", | ||
| ) | ||
| except Exception as e: | ||
| return CommandResult( | ||
| success=False, | ||
| message=f"Workspace error: {str(e)}", | ||
| error=str(e), | ||
| ) |
There was a problem hiding this comment.
Workspace commands fail if session is created but not started.
Same issue as memory commands: get_workspace_manager() returns None until start_session() is called. While _workspace_init handles this (lines 508-511), other subcommands like status, branch, commit, and history will raise AttributeError.
Proposed fix
def _handle_workspace(self, args: list) -> CommandResult:
"""Handle workspace commands."""
if not args:
return CommandResult(
success=False,
message="Workspace subcommand required. Use 'help workspace' for options.",
)
if not self.current_session:
return CommandResult(
success=False, message="No active session. Create one first."
)
+ workspace_mgr = self.session_manager.get_workspace_manager()
+ # Allow 'init' to proceed even if manager is None (it handles this case)
+ if workspace_mgr is None and args[0].lower() != "init":
+ return CommandResult(
+ success=False, message="Session not started. Run 'session start' first."
+ )
+
subcommand = args[0].lower()
subargs = args[1:]
try:
- workspace_mgr = self.session_manager.get_workspace_manager()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 470 - 503, The
workspace subcommands call self.session_manager.get_workspace_manager() which
can return None if the session exists but hasn't been started, causing
AttributeError for _workspace_status, _workspace_branch, _workspace_commit,
_workspace_history, and _workspace_list; update the dispatch in the method that
handles subcommand routing to check if workspace_mgr is None (after calling
get_workspace_manager()) and if so allow only the "init" subcommand to proceed
(delegate to _workspace_init) and return a clear CommandResult failure for other
subcommands instructing to start the session first (or call start_session) —
reference get_workspace_manager, _workspace_init, _workspace_status,
_workspace_branch, _workspace_commit, _workspace_history, and _workspace_list
when making the change.
| """Log entry for a session action.""" | ||
|
|
||
| id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Action ID") | ||
| timestamp: datetime = Field(default_factory=datetime.now, description="Action timestamp") |
There was a problem hiding this comment.
Timestamps use local time; coding guidelines require UTC with 'Z' suffix.
The SessionAction model uses datetime.now which produces local timestamps. This violates the coding guideline requiring UTC ISO format with 'Z' suffix.
Proposed fix
+from datetime import datetime, timedelta, timezone
...
class SessionAction(BaseModel):
"""Log entry for a session action."""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Action ID")
- timestamp: datetime = Field(default_factory=datetime.now, description="Action timestamp")
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), description="Action timestamp")As per coding guidelines: "Use UTC ISO format with 'Z' suffix for timestamps".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_session.py` at line 44, The SessionAction
model's timestamp Field currently uses datetime.now (local time); change its
default_factory to return a UTC-aware datetime (e.g., datetime.now(timezone.utc)
or datetime.utcnow().replace(tzinfo=timezone.utc)) so timestamps are in UTC and
will serialize with the 'Z' suffix per the guidelines, and add the necessary
import for timezone from datetime; update the timestamp Field definition in
SessionAction accordingly.
| timestamp: datetime = Field(default_factory=datetime.now, description="Event timestamp") | ||
| severity: EventSeverity = Field(default=EventSeverity.INFO, description="Event severity") | ||
|
|
||
| # Event context | ||
| message: str = Field(..., description="Event message/description") | ||
| context: Dict[str, Any] = Field(default_factory=dict, description="Additional context") | ||
|
|
||
| # Performance metrics (optional) | ||
| duration_ms: Optional[float] = Field(None, description="Duration in milliseconds") | ||
| metrics: Dict[str, Any] = Field(default_factory=dict, description="Performance metrics") | ||
|
|
||
| # Error information (if applicable) | ||
| error_message: Optional[str] = Field(None, description="Error message") | ||
| error_type: Optional[str] = Field(None, description="Error type/class name") | ||
|
|
||
| def to_dict(self) -> Dict[str, Any]: | ||
| """Convert event to dictionary.""" | ||
| return { | ||
| "event_type": self.event_type.value, | ||
| "agent_id": str(self.agent_id), | ||
| "timestamp": self.timestamp.isoformat(), |
There was a problem hiding this comment.
Persisted telemetry timestamps are local-naive instead of UTC Z.
datetime.now() plus isoformat() writes host-local timestamps without timezone info, so the JSONL stream is ambiguous across machines and violates the repo timestamp contract.
Suggested fix
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
@@
- timestamp: datetime = Field(default_factory=datetime.now, description="Event timestamp")
+ timestamp: datetime = Field(
+ default_factory=lambda: datetime.now(timezone.utc),
+ description="Event timestamp",
+ )
@@
- timestamp: datetime = Field(default_factory=datetime.now, description="Timestamp")
+ timestamp: datetime = Field(
+ default_factory=lambda: datetime.now(timezone.utc),
+ description="Timestamp",
+ )
@@
- "timestamp": self.timestamp.isoformat(),
+ "timestamp": self.timestamp.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"),As per coding guidelines, "Use UTC ISO format with 'Z' suffix for timestamps".
Also applies to: 111-111
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_telemetry.py` around lines 75 - 95, The
timestamp field uses datetime.now() and to_dict calls .isoformat(), producing
naive local timestamps; change the timestamp Field default_factory to produce a
UTC-aware datetime (e.g., use datetime.utcnow() with timezone.utc or
datetime.now(timezone.utc)) and ensure to_dict emits UTC ISO with a trailing 'Z'
(e.g., format or replace +00:00 with 'Z') so the JSONL stream contains explicit
UTC timestamps; update the timestamp Field declaration and the to_dict
serialization in agent_telemetry.py (affecting the timestamp Field and the
to_dict method) and apply the same pattern to the other occurrence noted around
the second timestamp usage.
| # Flush adapter if available | ||
| if self.adapter: | ||
| try: | ||
| self.adapter.flush() | ||
| except Exception as e: | ||
| logger.error(f"Error flushing telemetry adapter: {e}") |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use ArchitectureReport for telemetry flush/write/load failures.
These handlers only emit raw log strings, so callers cannot inspect, propagate, or test the failure path in a structured way.
As per coding guidelines, "Use structured reporting via ArchitectureReport model for error handling".
Also applies to: 385-397, 451-457
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_telemetry.py` around lines 355 - 360,
Replace raw logger.error calls in the adapter error handlers with structured
ArchitectureReport usage: when calling self.adapter.flush(),
self.adapter.write(...), and self.adapter.load(...) catch exceptions and create
an ArchitectureReport (including a clear operation id like
"telemetry.adapter.flush"/"telemetry.adapter.write"/"telemetry.adapter.load",
the exception message, stacktrace, and any relevant context such as agent id or
payload size), then emit or return that ArchitectureReport instead of a plain
log string so callers can inspect and propagate failures; update the three
handler blocks (the flush block around self.adapter.flush(), the write block
around adapter.write, and the load block) to construct and forward the
ArchitectureReport rather than only calling logger.error.
| def _rotate_events_file(self) -> None: | ||
| """Rotate events file when it exceeds max size.""" | ||
| try: | ||
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | ||
| rotated_name = f"events_{timestamp}.jsonl" | ||
| rotated_path = self.telemetry_dir / rotated_name | ||
| self.events_file.rename(rotated_path) | ||
| logger.debug(f"Rotated events file: {rotated_path}") |
There was a problem hiding this comment.
load_events() drops history after the first rotation.
_rotate_events_file() renames older segments to events_*.jsonl, but this loader only reads the live events.jsonl. After a rotation, callers lose most persisted telemetry.
Suggested fix
- if not self.events_file.exists():
- return []
-
- events = []
- try:
- with open(self.events_file, "r", encoding="utf-8") as f:
- for line in f:
- if line.strip():
- events.append(json.loads(line))
+ event_files = sorted(self.telemetry_dir.glob("events_*.jsonl"))
+ if self.events_file.exists():
+ event_files.append(self.events_file)
+ if not event_files:
+ return []
+
+ events = []
+ try:
+ for event_file in event_files:
+ with open(event_file, "r", encoding="utf-8") as f:
+ for line in f:
+ if line.strip():
+ events.append(json.loads(line))Also applies to: 440-459
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_telemetry.py` around lines 388 - 395,
load_events currently only reads the live events.jsonl so after
_rotate_events_file renames older segments to events_*.jsonl you lose history;
update load_events to also glob telemetry_dir for rotated files (e.g., matching
"events_*.jsonl" and "events.jsonl"), open and read them all in chronological
order (sort by filename/timestamp) before returning combined events, and ensure
this logic references telemetry_dir, _rotate_events_file and events_file so
rotated segments are included in the telemetry load.
| with patch("ghostclaw.core.agent_sdk.config.get_settings") as mock_settings: | ||
| with patch("ghostclaw.core.agent_sdk.agent_telemetry.bootstrap_telemetry"): | ||
| settings = MagicMock() | ||
| settings.memory_base_dir = tmp_path / "memory" | ||
| mock_settings.return_value = settings | ||
|
|
||
| mgr = AgentTelemetryManager(agent_id) | ||
| mgr.telemetry_dir = tmp_path / "telemetry" | ||
| mgr.telemetry_dir.mkdir(parents=True, exist_ok=True) | ||
| mgr.events_file = mgr.telemetry_dir / "events.jsonl" | ||
| return mgr |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Show where `get_settings` is imported in the source and compare the patch targets used by the tests.
rg -n --type=py 'from \.config import get_settings' src/ghostclaw/core/agent_sdk/agent_telemetry.py
rg -n --type=py 'patch\("ghostclaw\.core\.agent_sdk\.(config|agent_telemetry)\.get_settings"\)' tests/unit/test_agent_telemetry.py tests/integration/test_agent_sdk_integration.pyRepository: Ev3lynx727/ghostclaw
Length of output: 359
Patch the symbol where AgentTelemetryManager actually uses it.
agent_telemetry.py imports get_settings at line 24 with from .config import get_settings, binding it to the module's namespace. The integration fixture must patch ghostclaw.core.agent_sdk.agent_telemetry.get_settings (as the unit test correctly does), not ghostclaw.core.agent_sdk.config.get_settings. Patching the latter has no effect on the imported binding, so AgentTelemetryManager.__init__ still accesses the real settings and creates the actual telemetry directory before the test overrides mgr.telemetry_dir.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/integration/test_agent_sdk_integration.py` around lines 38 - 48, The
test patches ghostclaw.core.agent_sdk.config.get_settings but
AgentTelemetryManager imports get_settings into its own module namespace (from
.config import get_settings), so the fixture must patch
ghostclaw.core.agent_sdk.agent_telemetry.get_settings instead; update the test's
patch target to patch "ghostclaw.core.agent_sdk.agent_telemetry.get_settings"
(so AgentTelemetryManager.__init__ uses the mocked settings), keep returning a
MagicMock with memory_base_dir and ensure telemetry_dir setup as before to avoid
the real telemetry directory creation.
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
src/ghostclaw/core/agent_sdk/agent_cli.py (3)
43-51:⚠️ Potential issue | 🔴 CriticalPass a UUID into
AgentSessionManager, not the"default-agent"sentinel.Line 43 still defaults
agent_idto a plain string, and Line 51 forwards it unchanged. The downstream session/identity/memory/workspace managers are UUID-backed, so this path can still fail once they touch the id. If you want a human-readable prompt label, keep it separate from the internal identifier.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 43 - 51, The constructor currently defaults agent_id to the literal "default-agent" and passes it into AgentSessionManager which expects a UUID-backed identifier; change __init__ so that if no agent_id is provided you generate a UUID (e.g., uuid.uuid4()) and pass that UUID to AgentSessionManager(agent_id=...), while storing any human-readable label separately (e.g., self.display_name = provided_label or "default-agent") so downstream managers receive a proper UUID; update references to self.agent_id vs self.display_name accordingly.
369-381:⚠️ Potential issue | 🟠 MajorReturn a clear pre-start error before calling memory manager methods.
After
session create,get_memory_manager()can still beNone. The current branches pass thatNoneinto the memory helpers, so the user gets anAttributeErrorinstead of a clear “runsession startfirst” failure.Suggested fix
try: memory_mgr = self.session_manager.get_memory_manager() + if memory_mgr is None: + return CommandResult( + success=False, + message="Session not started. Run 'session start' first.", + ) if subcommand == "add": return self._memory_add(memory_mgr, subargs)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 369 - 381, session_manager.get_memory_manager() can return None after session create, so before calling any helpers (get_memory_manager(), _memory_add, _memory_list, _memory_search, _memory_stats, _memory_export) add a guard that checks if memory_mgr is None and return a clear, user-facing error like "memory manager not started: run `session start` first" (or raise the CLI/usage error your CLI framework expects) instead of passing None into the _memory_* helpers; place this check immediately after memory_mgr = self.session_manager.get_memory_manager() so all branches are protected.
483-497:⚠️ Potential issue | 🟠 MajorGate manager-dependent workspace commands on an initialized workspace manager.
After
session create,get_workspace_manager()can still beNone.branch,commit, andhistorythen fail onAttributeError, andstatusis still routed even though no workspace has been initialized yet.Suggested fix
try: workspace_mgr = self.session_manager.get_workspace_manager() + if workspace_mgr is None and subcommand not in {"init", "list"}: + return CommandResult( + success=False, + message="Session not started. Run 'session start' first.", + ) if subcommand == "init": return self._workspace_init(workspace_mgr, subargs)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 483 - 497, The code calls self.session_manager.get_workspace_manager() and then unconditionally routes many subcommands, which will raise AttributeError or operate when no workspace exists; change the dispatch so that only the "init" subcommand is allowed when workspace_mgr is None, and for other manager-dependent commands ("status", "branch", "commit", "history", "list") first verify workspace_mgr is non-None (and if your workspace manager exposes an initialization check, e.g., workspace_mgr.is_initialized(), verify that too) and return a clear error/exit early if not initialized; keep _workspace_init handling as-is and ensure other handlers (_workspace_status, _workspace_branch, _workspace_commit, _workspace_history, _workspace_list) are only invoked when the manager is present and initialized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/ghostclaw/core/agent_sdk/agent_cli.py`:
- Around line 532-541: The _workspace_status helper currently returns a
hardcoded healthy message; use workspace_mgr to determine real state instead.
Update _workspace_status to call workspace_mgr.is_initialized() (or
workspace_mgr.get_status()/verify_integrity() if available) and, if
uninitialized, return CommandResult(success=False, message="Workspace not
initialized" plus any workspace_mgr error details); if initialized, query any
diagnostic methods on workspace_mgr to detect corrupt/missing files and include
those details in the message and set success=False on errors, otherwise return
success=True with the real status text. Ensure you reference and surface
workspace_mgr's error/exception text rather than a fixed string so callers get
accurate status from _workspace_status.
- Around line 19-26: The CommandResult currently carries failures as freeform
strings; change it to use the repo-standard ArchitectureReport model for
structured errors by replacing or retyping the error field in CommandResult (and
any other CLI result constructs at the other occurrence) to accept an
ArchitectureReport (or Optional[ArchitectureReport]) instead of str, update
places that construct CommandResult on failure to build and pass an
ArchitectureReport instance, and update the top-level exception handling path
(the try/except that returns failure results around lines referencing the other
occurrence) to convert caught exceptions into an ArchitectureReport before
returning/propagating so all CLI failures use the ArchitectureReport contract.
---
Duplicate comments:
In `@src/ghostclaw/core/agent_sdk/agent_cli.py`:
- Around line 43-51: The constructor currently defaults agent_id to the literal
"default-agent" and passes it into AgentSessionManager which expects a
UUID-backed identifier; change __init__ so that if no agent_id is provided you
generate a UUID (e.g., uuid.uuid4()) and pass that UUID to
AgentSessionManager(agent_id=...), while storing any human-readable label
separately (e.g., self.display_name = provided_label or "default-agent") so
downstream managers receive a proper UUID; update references to self.agent_id vs
self.display_name accordingly.
- Around line 369-381: session_manager.get_memory_manager() can return None
after session create, so before calling any helpers (get_memory_manager(),
_memory_add, _memory_list, _memory_search, _memory_stats, _memory_export) add a
guard that checks if memory_mgr is None and return a clear, user-facing error
like "memory manager not started: run `session start` first" (or raise the
CLI/usage error your CLI framework expects) instead of passing None into the
_memory_* helpers; place this check immediately after memory_mgr =
self.session_manager.get_memory_manager() so all branches are protected.
- Around line 483-497: The code calls
self.session_manager.get_workspace_manager() and then unconditionally routes
many subcommands, which will raise AttributeError or operate when no workspace
exists; change the dispatch so that only the "init" subcommand is allowed when
workspace_mgr is None, and for other manager-dependent commands ("status",
"branch", "commit", "history", "list") first verify workspace_mgr is non-None
(and if your workspace manager exposes an initialization check, e.g.,
workspace_mgr.is_initialized(), verify that too) and return a clear error/exit
early if not initialized; keep _workspace_init handling as-is and ensure other
handlers (_workspace_status, _workspace_branch, _workspace_commit,
_workspace_history, _workspace_list) are only invoked when the manager is
present and initialized.
🪄 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: efd25cec-0302-4695-b2df-411c1d2da74a
📒 Files selected for processing (1)
src/ghostclaw/core/agent_sdk/agent_cli.py
| @dataclass | ||
| class CommandResult: | ||
| """Result of a CLI command execution.""" | ||
|
|
||
| success: bool | ||
| message: str | ||
| error: Optional[str] = None | ||
| data: Any = field(default=None) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use ArchitectureReport for failures.
The new public result type and top-level exception path both encode failures as freeform strings. That gives the CLI a second error contract instead of the repo-standard structured one.
As per coding guidelines, "Use structured reporting via ArchitectureReport model for error handling".
Also applies to: 96-101
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 19 - 26, The
CommandResult currently carries failures as freeform strings; change it to use
the repo-standard ArchitectureReport model for structured errors by replacing or
retyping the error field in CommandResult (and any other CLI result constructs
at the other occurrence) to accept an ArchitectureReport (or
Optional[ArchitectureReport]) instead of str, update places that construct
CommandResult on failure to build and pass an ArchitectureReport instance, and
update the top-level exception handling path (the try/except that returns
failure results around lines referencing the other occurrence) to convert caught
exceptions into an ArchitectureReport before returning/propagating so all CLI
failures use the ArchitectureReport contract.
| def _workspace_status(self, workspace_mgr, args: list) -> CommandResult: | ||
| """Get workspace status.""" | ||
| try: | ||
| # Get basic status | ||
| status_text = "Workspace Status\n" | ||
| status_text += " Repository initialized and ready\n" | ||
| return CommandResult( | ||
| success=True, | ||
| message=status_text, | ||
| ) |
There was a problem hiding this comment.
workspace status currently reports a fake healthy state.
This helper ignores workspace_mgr entirely and always returns “Repository initialized and ready”. That means the command succeeds before workspace init and after init failures, so callers can't trust it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ghostclaw/core/agent_sdk/agent_cli.py` around lines 532 - 541, The
_workspace_status helper currently returns a hardcoded healthy message; use
workspace_mgr to determine real state instead. Update _workspace_status to call
workspace_mgr.is_initialized() (or workspace_mgr.get_status()/verify_integrity()
if available) and, if uninitialized, return CommandResult(success=False,
message="Workspace not initialized" plus any workspace_mgr error details); if
initialized, query any diagnostic methods on workspace_mgr to detect
corrupt/missing files and include those details in the message and set
success=False on errors, otherwise return success=True with the real status
text. Ensure you reference and surface workspace_mgr's error/exception text
rather than a fixed string so callers get accurate status from
_workspace_status.
- Fix UUID default_factory crashes (models.py): Use uuid4 instead of UUID for AgentMessage, Suggestion, AgentSession - Fix AgentPersonality default_factory: Use lambda factory with name='default' - Fix duplicate SessionAction creation: Refactor _log_action to accept SessionAction object - Fix string agent_id vs UUID type mismatch: Convert agent_id to UUID using uuid5 for managers - Fix memory file corruption: Raise RuntimeError instead of silently overwriting corrupt files - Fix path traversal vulnerability: Add path containment checks in workspace read/write operations All 184 tests passing for agent SDK, CLI, session, memory, and workspace modules.
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/ghostclaw/core/agent_sdk/agent_session.py (1)
44-44:⚠️ Potential issue | 🟠 Major | ⚡ Quick winTimestamps still use local time across this module.
SessionAction.timestamp(line 44) and everydatetime.now()call in the manager (lines 127, 159, 187, 218, 244, 277, 414) produce naive local datetimes, which conflicts with the coding guideline requiring UTC ISO format withZsuffix. Switch todatetime.now(timezone.utc)and ensure ISO emission usesZ(e.g.,replace("+00:00", "Z")) on all the.isoformat()call sites inexport_session_dataand_save_session_metadata.As per coding guidelines: "Use UTC ISO format with 'Z' suffix for timestamps".
🤖 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 `@src/ghostclaw/core/agent_sdk/agent_session.py` at line 44, SessionAction.timestamp and all datetime.now() usages in the manager must produce UTC-aware timestamps and emission must use ISO with a trailing Z; change Field(default_factory=datetime.now) for SessionAction.timestamp to default_factory=lambda: datetime.now(timezone.utc), replace all plain datetime.now() calls in the manager with datetime.now(timezone.utc), and update every .isoformat() call in export_session_data and _save_session_metadata to emit Z (e.g., dt.isoformat().replace("+00:00", "Z") or dt.replace(tzinfo=timezone.utc).isoformat().replace("+00:00", "Z")) so timestamps are UTC-aware and follow the "YYYY-MM-DDTHH:MM:SSZ" guideline.
🧹 Nitpick comments (2)
src/ghostclaw/core/agent_sdk/agent_session.py (1)
203-205: ⚡ Quick winBare
except Exceptionblocks silently swallow real errors.Every lifecycle method (
start_session,pause_session,resume_session,end_session,load_session,cleanup_session) catchesExceptionand returnsFalse/Nonewith no logging. Failures like aSessionSummaryvalidation error inend_session(which happens if_session_id is None) or a JSON decode failure inload_sessionwill be invisible to callers and tests, making field debugging very hard.At minimum, log the exception (e.g., via the standard
loggingmodule) before returning, and consider re-raising programmer errors (e.g., callingend_sessionbeforecreate_session) so the caller can distinguish "couldn't load" from "code bug". This also fits the coding guideline preference for structured reporting.Also applies to: 229-230, 258-259, 314-315, 491-492, 517-518
🤖 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 `@src/ghostclaw/core/agent_sdk/agent_session.py` around lines 203 - 205, The lifecycle methods (start_session, pause_session, resume_session, end_session, load_session, cleanup_session) currently use bare except Exception blocks and silently return False/None; update each except to log the exception using the standard logging module (include stack/info via logger.exception or logger.error(..., exc_info=True)) and return as before, and where the failure indicates a programmer error (e.g., end_session when _session_id is None or validation errors from SessionSummary) raise a specific exception (ValueError or RuntimeError) instead of swallowing it so callers/tests can distinguish bugs from operational failures; reference the SessionState transitions and the methods named above when making these changes.src/ghostclaw/core/agent_sdk/agent_memory.py (1)
168-168: ⚡ Quick win
entry_idis collision-prone — preferuuid4.
f"{prefix}_{len(memory.entries)}_{int(...timestamp())}"collides if an entry is deleted and a new one added within the same second (length resets to a previously used value). Given the rest of the SDK switched touuid4for stable IDs, do the same here.🔧 Proposed fix
+import uuid @@ - entry_id = f"{memory_type.split('.')[0].lower()}_{len(memory.entries)}_{int(datetime.now().timestamp())}" + entry_id = f"{memory_type.split('.')[0].lower()}_{uuid.uuid4().hex}"🤖 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 `@src/ghostclaw/core/agent_sdk/agent_memory.py` at line 168, Replace the fragile timestamp-based ID construction for entry_id with a UUIDv4-based ID: instead of building f"{memory_type.split('.')[0].lower()}_{len(memory.entries)}_{int(datetime.now().timestamp())}", generate a stable random id using uuid.uuid4() (e.g., hex or str form) and keep the same prefix if needed (use memory_type.split('.')[0].lower() to preserve context); add the uuid import at the top of agent_memory.py and update the code path where entry_id is created to use the uuid4-based value so IDs no longer collide when entries are deleted and re-added (refer to the variables memory_type and memory.entries to find the exact spot).
🤖 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 `@src/ghostclaw/core/agent_sdk/agent_memory.py`:
- Around line 432-458: The clear_memory logic has unused and unsafe code: remove
the unused file_deleted variable, ensure original_count is defined before it's
referenced by moving its assignment outside the conditional (or only reference
it inside the before_date branch), and simplify the save condition so files are
only written when entries were actually removed (i.e., if before_date: save only
when original_count != len(memory.entries); else when clearing all: only save if
memory.entries was non-empty before clearing). Also validate a provided
memory_type against self.MEMORY_FILES (reject/ignore invalid names) to avoid
creating stray files via _save_memory_file, and continue skipping
self.INDEX_FILE as already intended in
clear_memory/_load_memory_file/_save_memory_file usage.
- Around line 32-46: MemoryEntry.created_at/updated_at and
MemoryFile.created_at/updated_at use naive datetime.now(); replace those with
timezone-aware UTC timestamps by using datetime.now(timezone.utc) for all
default_factory calls in the MemoryEntry and MemoryFile models and in every
other timestamp call site in this module (the manager functions noted in the
review). Also ensure any serialization emits a Z-suffixed ISO string by
converting to UTC and replacing the "+00:00" suffix (e.g., use
astimezone(timezone.utc).isoformat().replace("+00:00","Z")) so all emitted
timestamps follow the UTC ISO format with a trailing Z.
- Line 22: Remove the unused import of AgentIdentity from this module to satisfy
Ruff F401; specifically delete or stop importing "AgentIdentity" from the
statement "from .models import AgentIdentity" in
src/ghostclaw/core/agent_sdk/agent_memory.py so the module only imports names
that are actually referenced.
In `@src/ghostclaw/core/agent_sdk/agent_session.py`:
- Around line 451-492: load_session currently only restores basic fields and
drops actions, paused state/durations and may not restore
SessionMetrics.total_duration when serialized as a timedelta string; update
load_session to (1) read and assign the serialized actions into the agent’s
actions storage (so get_actions() returns the persisted actions), (2) restore
paused state by reading and setting _paused_at and _paused_duration (convert
stored seconds or string back into datetime/timedelta as appropriate), and (3)
reconstruct _metrics so total_duration is restored (parse a stringified
timedelta or seconds into SessionMetrics.total_duration) using the
SessionMetrics constructor or by setting fields directly; also update
export_session_data (the writer used by export_session_data) to include
paused_duration (e.g., seconds) and a stable serialization for total_duration so
the round-trip is lossless.
---
Duplicate comments:
In `@src/ghostclaw/core/agent_sdk/agent_session.py`:
- Line 44: SessionAction.timestamp and all datetime.now() usages in the manager
must produce UTC-aware timestamps and emission must use ISO with a trailing Z;
change Field(default_factory=datetime.now) for SessionAction.timestamp to
default_factory=lambda: datetime.now(timezone.utc), replace all plain
datetime.now() calls in the manager with datetime.now(timezone.utc), and update
every .isoformat() call in export_session_data and _save_session_metadata to
emit Z (e.g., dt.isoformat().replace("+00:00", "Z") or
dt.replace(tzinfo=timezone.utc).isoformat().replace("+00:00", "Z")) so
timestamps are UTC-aware and follow the "YYYY-MM-DDTHH:MM:SSZ" guideline.
---
Nitpick comments:
In `@src/ghostclaw/core/agent_sdk/agent_memory.py`:
- Line 168: Replace the fragile timestamp-based ID construction for entry_id
with a UUIDv4-based ID: instead of building
f"{memory_type.split('.')[0].lower()}_{len(memory.entries)}_{int(datetime.now().timestamp())}",
generate a stable random id using uuid.uuid4() (e.g., hex or str form) and keep
the same prefix if needed (use memory_type.split('.')[0].lower() to preserve
context); add the uuid import at the top of agent_memory.py and update the code
path where entry_id is created to use the uuid4-based value so IDs no longer
collide when entries are deleted and re-added (refer to the variables
memory_type and memory.entries to find the exact spot).
In `@src/ghostclaw/core/agent_sdk/agent_session.py`:
- Around line 203-205: The lifecycle methods (start_session, pause_session,
resume_session, end_session, load_session, cleanup_session) currently use bare
except Exception blocks and silently return False/None; update each except to
log the exception using the standard logging module (include stack/info via
logger.exception or logger.error(..., exc_info=True)) and return as before, and
where the failure indicates a programmer error (e.g., end_session when
_session_id is None or validation errors from SessionSummary) raise a specific
exception (ValueError or RuntimeError) instead of swallowing it so callers/tests
can distinguish bugs from operational failures; reference the SessionState
transitions and the methods named above when making these changes.
🪄 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: 1a0145c0-8ca8-4943-b491-89cb6973ecac
📒 Files selected for processing (6)
src/ghostclaw/core/agent_sdk/agent_cli.pysrc/ghostclaw/core/agent_sdk/agent_memory.pysrc/ghostclaw/core/agent_sdk/agent_sdk.pysrc/ghostclaw/core/agent_sdk/agent_session.pysrc/ghostclaw/core/agent_sdk/agent_workspace.pysrc/ghostclaw/core/agent_sdk/models.py
✅ Files skipped from review due to trivial changes (1)
- src/ghostclaw/core/agent_sdk/models.py
🚧 Files skipped from review as they are similar to previous changes (3)
- src/ghostclaw/core/agent_sdk/agent_sdk.py
- src/ghostclaw/core/agent_sdk/agent_cli.py
- src/ghostclaw/core/agent_sdk/agent_workspace.py
| from pydantic import BaseModel, Field | ||
|
|
||
| from .config import AgentSDKSettings | ||
| from .models import AgentIdentity |
There was a problem hiding this comment.
Remove unused AgentIdentity import — failing CI lint.
Ruff F401 is failing the lint job. AgentIdentity is imported but never referenced in this module.
🔧 Proposed fix
-from .config import AgentSDKSettings
-from .models import AgentIdentity
+from .config import AgentSDKSettings📝 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.
| from .models import AgentIdentity | |
| from .config import AgentSDKSettings |
🧰 Tools
🪛 GitHub Actions: CI / 2_lint.txt
[error] 22-22: Ruff F401: .models.AgentIdentity imported but unused. Remove unused import .models.AgentIdentity.
🪛 GitHub Actions: CI / lint
[error] 22-22: ruff (F401) unused import: .models.AgentIdentity.
🤖 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 `@src/ghostclaw/core/agent_sdk/agent_memory.py` at line 22, Remove the unused
import of AgentIdentity from this module to satisfy Ruff F401; specifically
delete or stop importing "AgentIdentity" from the statement "from .models import
AgentIdentity" in src/ghostclaw/core/agent_sdk/agent_memory.py so the module
only imports names that are actually referenced.
| files_to_clear = [memory_type] if memory_type else self.MEMORY_FILES | ||
|
|
||
| for memory_file in files_to_clear: | ||
| if memory_file == self.INDEX_FILE: | ||
| continue | ||
|
|
||
| memory = self._load_memory_file(memory_file) | ||
|
|
||
| if before_date: | ||
| # Delete entries older than before_date | ||
| original_count = len(memory.entries) | ||
| memory.entries = [ | ||
| e for e in memory.entries | ||
| if e.created_at >= before_date | ||
| ] | ||
| deleted_count += original_count - len(memory.entries) | ||
| else: | ||
| # Delete all entries | ||
| deleted_count += len(memory.entries) | ||
| memory.entries = [] | ||
|
|
||
| file_deleted = (original_count - len(memory.entries)) if before_date else (deleted_count - (deleted_count - len(memory.entries) if not before_date else 0)) | ||
| if len(memory.entries) == 0 or (before_date and original_count != len(memory.entries)) or (not before_date): | ||
| memory.updated_at = datetime.now() | ||
| self._save_memory_file(memory_file, memory) | ||
|
|
||
| return deleted_count |
There was a problem hiding this comment.
clear_memory has dead code, a lint failure, and an over-broad save condition — simplify.
Several problems compound here:
- Line 453 assigns
file_deletedand never uses it — Ruff F841 is failing CI. original_countis only defined inside theif before_date:branch but is referenced unconditionally on line 453, so the expression is meaningful only by accident of short-circuit evaluation.- The save guard on line 454 is convoluted:
(not before_date)isTruewheneverbefore_date is None, so the file is always rewritten in the "clear all" path even ifmemory.entrieswas already empty. For thebefore_datepath, thelen(memory.entries) == 0clause causes a save even when nothing was deleted (e.g. an already-empty file). - When
memory_typeis provided as a single string,files_to_clearcontains one element but no validation againstMEMORY_FILES, so a typo silently creates a stray file via_save_memory_file.
🔧 Proposed fix
def clear_memory(
self,
memory_type: Optional[str] = None,
before_date: Optional[datetime] = None,
) -> int:
...
if not self._is_initialized:
self.initialize()
deleted_count = 0
- files_to_clear = [memory_type] if memory_type else self.MEMORY_FILES
+ if memory_type is not None and memory_type not in self.MEMORY_FILES:
+ raise ValueError(f"Invalid memory type: {memory_type}")
+ files_to_clear = [memory_type] if memory_type else self.MEMORY_FILES
for memory_file in files_to_clear:
if memory_file == self.INDEX_FILE:
continue
memory = self._load_memory_file(memory_file)
-
- if before_date:
- # Delete entries older than before_date
- original_count = len(memory.entries)
- memory.entries = [
- e for e in memory.entries
- if e.created_at >= before_date
- ]
- deleted_count += original_count - len(memory.entries)
- else:
- # Delete all entries
- deleted_count += len(memory.entries)
- memory.entries = []
-
- file_deleted = (original_count - len(memory.entries)) if before_date else (deleted_count - (deleted_count - len(memory.entries) if not before_date else 0))
- if len(memory.entries) == 0 or (before_date and original_count != len(memory.entries)) or (not before_date):
- memory.updated_at = datetime.now()
- self._save_memory_file(memory_file, memory)
+ original_count = len(memory.entries)
+ if before_date:
+ memory.entries = [e for e in memory.entries if e.created_at >= before_date]
+ else:
+ memory.entries = []
+
+ removed = original_count - len(memory.entries)
+ if removed > 0:
+ deleted_count += removed
+ memory.updated_at = datetime.now(timezone.utc)
+ self._save_memory_file(memory_file, memory)
return deleted_count🧰 Tools
🪛 GitHub Actions: CI / 2_lint.txt
[error] 453-454: Ruff F841: Local variable file_deleted is assigned to but never used. Remove assignment to unused variable file_deleted.
🪛 GitHub Actions: CI / lint
[error] 453-453: ruff (F841) local variable file_deleted is assigned to but never used.
🤖 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 `@src/ghostclaw/core/agent_sdk/agent_memory.py` around lines 432 - 458, The
clear_memory logic has unused and unsafe code: remove the unused file_deleted
variable, ensure original_count is defined before it's referenced by moving its
assignment outside the conditional (or only reference it inside the before_date
branch), and simplify the save condition so files are only written when entries
were actually removed (i.e., if before_date: save only when original_count !=
len(memory.entries); else when clearing all: only save if memory.entries was
non-empty before clearing). Also validate a provided memory_type against
self.MEMORY_FILES (reject/ignore invalid names) to avoid creating stray files
via _save_memory_file, and continue skipping self.INDEX_FILE as already intended
in clear_memory/_load_memory_file/_save_memory_file usage.
| def load_session(self, session_id: UUID) -> bool: | ||
| """ | ||
| Load a previous session by ID. | ||
|
|
||
| Args: | ||
| session_id: Session ID to load | ||
|
|
||
| Returns: | ||
| True if successful, False otherwise | ||
| """ | ||
| try: | ||
| session_file = self.session_root / f"session_{session_id}.json" | ||
|
|
||
| if not session_file.exists(): | ||
| return False | ||
|
|
||
| with open(session_file, 'r', encoding='utf-8') as f: | ||
| data = json.load(f) | ||
|
|
||
| # Restore session data | ||
| self._session_id = UUID(data["session_id"]) | ||
| self._state = SessionState(data["state"]) | ||
| self._created_at = datetime.fromisoformat(data["created_at"]) | ||
|
|
||
| if data.get("started_at"): | ||
| self._started_at = datetime.fromisoformat(data["started_at"]) | ||
|
|
||
| if data.get("ended_at"): | ||
| self._ended_at = datetime.fromisoformat(data["ended_at"]) | ||
|
|
||
| # Restore context | ||
| context_data = data.get("context", {}) | ||
| if "project_path" in context_data and context_data["project_path"]: | ||
| context_data["project_path"] = Path(context_data["project_path"]) | ||
| self._context = SessionContext(**context_data) | ||
|
|
||
| # Restore metrics | ||
| self._metrics = SessionMetrics(**data.get("metrics", {})) | ||
|
|
||
| return True | ||
| except Exception: | ||
| return False |
There was a problem hiding this comment.
load_session performs only a partial restore — actions, paused state, and durations are dropped.
export_session_data writes actions and the manager tracks _paused_duration / _paused_at, but load_session never reads them back. After a load, get_actions() returns [] even though the JSON contains them, and get_duration() ignores any time spent paused before persistence, so subsequent end_session summaries will be wrong for any session that was paused. Also, SessionMetrics(**data.get("metrics", {})) re-parses metrics fine in v2 but won't restore total_duration if it was serialized as a stringified timedelta via default=str in _save_session_data.
🔧 Suggested restoration
# Restore metrics
- self._metrics = SessionMetrics(**data.get("metrics", {}))
+ self._metrics = SessionMetrics(**data.get("metrics", {}))
+
+ # Restore actions
+ self._actions = [
+ SessionAction(**a) for a in data.get("actions", [])
+ ]
+
+ # Restore paused duration if present
+ paused = data.get("paused_duration")
+ if paused is not None:
+ self._paused_duration = timedelta(seconds=float(paused))
return TrueYou'll also want export_session_data to include paused_duration (e.g., self._paused_duration.total_seconds()) so the round-trip is lossless.
🤖 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 `@src/ghostclaw/core/agent_sdk/agent_session.py` around lines 451 - 492,
load_session currently only restores basic fields and drops actions, paused
state/durations and may not restore SessionMetrics.total_duration when
serialized as a timedelta string; update load_session to (1) read and assign the
serialized actions into the agent’s actions storage (so get_actions() returns
the persisted actions), (2) restore paused state by reading and setting
_paused_at and _paused_duration (convert stored seconds or string back into
datetime/timedelta as appropriate), and (3) reconstruct _metrics so
total_duration is restored (parse a stringified timedelta or seconds into
SessionMetrics.total_duration) using the SessionMetrics constructor or by
setting fields directly; also update export_session_data (the writer used by
export_session_data) to include paused_duration (e.g., seconds) and a stable
serialization for total_duration so the round-trip is lossless.
| if not line.strip(): | ||
| continue | ||
|
|
||
| parts = line.split('|') |
There was a problem hiding this comment.
🟡 Git commit history parsing breaks on commit messages containing '|' character
In get_commit_history(), the git log format uses | as a field separator (--format=%H|%s|%an|%ai|%an), and the output is parsed with an unbounded line.split('|'). If a commit message contains | (e.g., "Fix: use A|B pattern"), the split produces extra parts, causing the message (parts[1]) to be truncated at the first |, the author (parts[2]) to contain part of the message, and the timestamp (parts[3]) to parse the wrong field entirely—likely raising a ValueError from datetime.fromisoformat() (silently caught by the blanket except Exception).
Prompt for agents
The git log format in get_commit_history() (agent_workspace.py line 329) uses | as delimiter: --format=%H|%s|%an|%ai|%an. When commit messages contain |, line.split('|') on line 337 produces incorrect field alignment, corrupting message, author, and timestamp parsing. The fix should use a delimiter that cannot appear in git fields. For example, use a null byte separator: change the format to --format=%H%x00%s%x00%an%x00%ai%x00%an and split on '\x00' instead of '|'. Alternatively, use split('|', maxsplit=4) but this still fails if commit subjects contain |.
Was this helpful? React with 👍 or 👎 to provide feedback.
| AgentStatus, | ||
| AgentType, | ||
| MessageRole, | ||
| SessionContext, |
There was a problem hiding this comment.
🔴 Public API exports wrong SessionContext class, shadowing the one used by AgentSessionManager
The package __init__.py (line 55) exports SessionContext from models.py, which has completely different fields (project_path: str required, scan_id, branch, files_analyzed, etc.) than the SessionContext from agent_session.py (which has project_path: Optional[Path], goals, metadata, tags). The AgentSessionManager.create_session() and start_session() expect the agent_session.SessionContext and access self._context.goals at agent_session.py:197. A user who imports SessionContext from the public package API and passes it to AgentSessionManager will get an AttributeError at runtime because models.SessionContext has no goals attribute.
Prompt for agents
There are two conflicting SessionContext classes: one in models.py (designed for analysis tracking with fields like scan_id, branch, files_analyzed) and one in agent_session.py (designed for session management with fields like goals, metadata, tags). The __init__.py at line 55 exports the models.py version, but AgentSessionManager expects the agent_session.py version. This means from ghostclaw.core.agent_sdk import SessionContext gives users the wrong class for session management. To fix: either (1) rename one of the classes (e.g., models.SessionContext -> AnalysisContext), or (2) export the agent_session.SessionContext instead/additionally, or (3) document clearly which SessionContext is for which purpose. The key issue is that agent_session.py line 197 accesses self._context.goals which doesn't exist on models.SessionContext.
Was this helpful? React with 👍 or 👎 to provide feedback.
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
| hash=parts[0][:7], | ||
| message=parts[1], | ||
| author=parts[2], | ||
| timestamp=datetime.fromisoformat(parts[3].replace(' ', 'T')), |
There was a problem hiding this comment.
🔴 get_commit_history always returns empty list due to broken timestamp parsing
The replace(' ', 'T') on line 343 replaces ALL spaces in the git %ai date string, including the space between the time and the timezone offset. For example, the git output 2026-05-07 09:58:22 +0700 becomes 2026-05-07T09:58:22T+0700, which is invalid ISO 8601 format and causes datetime.fromisoformat() to raise ValueError. Because the entire method is wrapped in a try/except Exception: return [], the error is silently swallowed and get_commit_history() always returns an empty list for any repository with commits.
| timestamp=datetime.fromisoformat(parts[3].replace(' ', 'T')), | |
| timestamp=datetime.fromisoformat(parts[3].strip().replace(' ', 'T', 1)), |
Was this helpful? React with 👍 or 👎 to provide feedback.
| for line in result.split('\n'): | ||
| if 'create mode' in line or 'changed' in line: | ||
| continue | ||
| if line.strip(): | ||
| return line.split()[-1] if line.split() else "committed" | ||
|
|
||
| return "committed" |
There was a problem hiding this comment.
🟡 commit_changes returns last word of commit message instead of commit hash
The method's docstring says it returns the "Commit hash or None on failure", but the parsing logic on lines 240-244 iterates git's stdout, skips lines containing 'create mode' or 'changed', and returns the last word of the first remaining line. Git commit output looks like [main abc1234] Initial commit — the last word is commit, not the hash abc1234. This means the method always returns the wrong value (the last word of the commit message instead of the commit hash).
Prompt for agents
The commit_changes method in agent_workspace.py attempts to parse the commit hash from git's stdout by taking the last word of the first non-skipped line, but git commit output like '[main abc1234] Initial commit' makes the last word 'commit', not the hash. The proper fix is to run a separate git command after committing to get the actual hash, e.g. `self._run_git_command(['rev-parse', 'HEAD'])` and return its output (trimmed). Alternatively, the git log format or rev-parse can be used to reliably extract the commit hash. The current parsing approach is fundamentally unreliable because commit messages are free-form text.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary by CodeRabbit