Skip to content

Feature Agent sdk build in - #28

Open
Ev3lynx727 wants to merge 12 commits into
developfrom
Feature-Agent-sdk-build-in
Open

Feature Agent sdk build in#28
Ev3lynx727 wants to merge 12 commits into
developfrom
Feature-Agent-sdk-build-in

Conversation

@Ev3lynx727

@Ev3lynx727 Ev3lynx727 commented Apr 3, 2026

Copy link
Copy Markdown
Owner
  • 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

Open with Devin

Summary by CodeRabbit

  • New Features
    • Agent SDK foundation: persistent agent identity & multi-file memory, interactive multi-turn agent sessions, isolated workspace flow, CLI agent commands, task orchestration, and local telemetry/metrics.
  • Documentation
    • Dozens of architecture, roadmap, and design docs covering backend/service migration, mission control, knowledge DB, agent modes, and phased implementation.
  • Tests
    • Large unit and integration test coverage for CLI, sessions, identity, memory, workspace, orchestrator, telemetry, and SDK.
  • Chores
    • Version bumped to 0.3.0 and changelog updated.

Ev3lynx727 added 3 commits March 31, 2026 08:23
- 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
@codesandbox

codesandbox Bot commented Apr 3, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@coderabbitai

coderabbitai Bot commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Ev3lynx727 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 45 minutes and 20 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83e9a94c-0de1-489d-8d7c-36fab9f20c83

📥 Commits

Reviewing files that changed from the base of the PR and between 4c8edc5 and 4fccfde.

📒 Files selected for processing (1)
  • src/ghostclaw/core/agent_sdk/agent_task_orchestrator.py
📝 Walkthrough

Walkthrough

Adds 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.

Changes

Agent SDK + Docs + Tests (v0.3.0)

Layer / File(s) Summary
Data Shape / Settings
src/ghostclaw/core/agent_sdk/models.py, src/ghostclaw/core/agent_sdk/config.py
Introduces Pydantic models/enums for agents, messages, suggestions, sessions, tasks, registry/metadata and adds AgentSDK/Session/Memory settings with env-backed defaults and singleton accessor.
Serialization / Utilities
src/ghostclaw/core/agent_sdk/serializers.py
Adds JSON encoder for UUID/Path/datetime, helpers to serialize/deserialize models, model<->json-dict converters, and ModelSerializer file I/O utilities.
Core Managers / Domain Logic
src/ghostclaw/core/agent_sdk/agent_identity.py, .../agent_memory.py, .../agent_workspace.py, .../agent_session.py, .../agent_telemetry.py
Implements AgentIdentityManager (disk identity), AgentMemoryManager (seven memory files, index/search/CRUD, persistence), AgentWorkspaceManager (isolated workspaces, git ops, PR creation), AgentSessionManager (session lifecycle, actions, metrics, persistence), and AgentTelemetryManager (buffered JSONL events, rotation, adapters).
Orchestration & SDK
src/ghostclaw/core/agent_sdk/agent_task_orchestrator.py, src/ghostclaw/core/agent_sdk/agent_sdk.py
Adds in-memory AgentTaskOrchestrator (tasks/plans/deps/execution lifecycle, stats) and AgentSDK high-level coordinator exposing session/identity/memory/workspace/CLI operations and safe gating.
CLI / UX Wiring
src/ghostclaw/core/agent_sdk/agent_cli.py, src/ghostclaw/core/agent_sdk/__init__.py
Adds interactive AgentCLI command dispatcher with CommandResult, wiring to managers, and package export surface (all, metadata, version).
Task Orchestrator Docs
docs/TASK_ORCHESTRATOR.md, ORCHESTRATOR_TESTING_SUMMARY.md
Documents the task orchestrator API, concepts, examples, and test-summary for orchestrator behavior.
Tests — Unit & Integration
tests/unit/*, tests/integration/test_agent_sdk_integration.py
Adds extensive unit tests for models, serializers, settings, identity/memory/workspace/session managers, orchestrator, telemetry, AgentSDK, AgentCLI and integration tests combining orchestrator+telemetry.
Architecture / Planning / Docs
AGENT_CAPABILITIES_ARCHITECTURE.md, BACKEND_ARCHITECTURE_BLUEPRINT.md, CLI_VS_SERVICE_DECISION.md, INTERACTIVE_AGENT_ARCHITECTURE.md, KNOWLEDGE_DB_ARCHITECTURE.md, MISSION_CONTROL_ARCHITECTURE.md, UNIFIED_ARCHITECTURE_BLUEPRINT.md, REVIEW.md, REVIEW_FRONEND.md, TASK.md, PHASE1_PROGRESS.md, CHANGELOG.md
Adds many new architecture/design/roadmap/progress documents describing agent lifecycle, backend migration, knowledge DB schemas, Mission Control, unified blueprint, Phase 1 progress, changelog entry for v0.3.0, and migration notes.
Manifests / Versioning
package.json, pyproject.toml, src/ghostclaw/version.py
Bumps project/package version to 0.3.0 and updates the public version constant.

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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Ev3lynx727/ghostclaw#26: Related changes to package/manifests and versioning that overlap manifest/version updates.

Poem

🐇
I hop through docs and code with cheer,
Memory files and branches appear,
Agents wake and sessions sing,
Commits and metrics — what a spring!
v0.3.0 — a rabbit's jubilant cheer.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Feature-Agent-sdk-build-in

devin-ai-integration[bot]

This comment was marked as resolved.

- 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.
devin-ai-integration[bot]

This comment was marked as resolved.

Ev3lynx727 and others added 4 commits April 3, 2026 23:01
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>
devin-ai-integration[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 | 🟠 Major

Avoid 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 | 🟠 Major

Periodic-save logic has two correctness issues.

Line 703 can crash when self.last_save is None, and len(self.dirty_files) >= 10 is 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

AsyncSession does not support .query() method in SQLAlchemy 2.x—code will fail at runtime.

Lines 633–668 use await db.query(Analysis).filter(...) in get_analysis() and list_analyses(), which is unsupported. AsyncSession requires the modern 2.0 API:

Use await db.execute(select(Analysis).where(...)) then .scalars(), or directly call await 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 | 🟠 Major

Remove the return response statement from chat_turn — async generators cannot return values.

The code snippet mixes yield with return response, which is a SyntaxError in Python. Per PEP 525, non-empty return statements are forbidden in async generator functions. Either refactor to use yield response as 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 | 🟠 Major

Fix async generator semantics and typing in example code.

The chat_turn function at lines 372–427 mixes async-generator behavior (yield) with return response, which is a SyntaxError in Python (PEP 525). Async generators cannot return non-None values. Additionally, line 432 uses Dict[str, any] instead of the correct Dict[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 | 🟠 Major

Flaky test: asserts incorrect default branch "develop".

The test expects get_current_branch() to return "develop" after initialization, but initialize_repo() runs git init without 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:

  1. Assert the actual default branch returned by git init, or
  2. 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 | 🟠 Major

List files from the isolated workspace, not from the original project path.

The active workspace lives behind workspace_mgr, but this command globs current_session["project_path"] instead. Users will see a different tree than the session's actual workspace, and manager-level filtering like .git exclusion 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 | 🟠 Major

Store the same action you return, and count it.

log_action() constructs one SessionAction to return, then _log_action() constructs a second one with a different id/timestamp and appends that instead. On top of that, SessionMetrics.action_count is never incremented, so callers get an action handle that does not exist in _actions and 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 | 🟠 Major

Check 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(), or commit_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 | 🟠 Major

Propagate the manager result instead of always returning True.

initialize_repo() returns False, and create_branch() / commit_changes() return None on 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 | 🟠 Major

This does not return a commit hash.

For normal git commit output 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 read HEAD after 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 | 🟠 Major

Don't hardcode develop as the active branch.

initialize_repo() never syncs _current_branch with the repository's real HEAD, so repos on main, master, or a remote-specific default still look like they're on develop. get_status() and push_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 | 🟠 Major

Account for an in-progress pause before computing total_duration.

If the session is ended directly from PAUSED, _paused_at is 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 | 🟠 Major

Reject identities that belong to a different agent.

from_dict() saves whatever data["id"] contains, while update() explicitly rejects identity.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 | 🟠 Major

Fix the public return contract for search_memory().

The signature/docstring promise a List[Dict[str, Any]], but AgentMemoryManager.search_all() returns Dict[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 | 🟠 Major

Rebuild INDEX.md after any destructive memory change.

add_entry() appends to the index, but update_entry(), delete_entry(), and clear_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 | 🟠 Major

The git log timestamp parsing makes valid entries unreadable.

%ai produces values like 2026-04-03 12:34:56 +0000; replacing every space with T yields 2026-04-03T12:34:56T+0000, which datetime.fromisoformat() cannot parse. Because the whole method sits under one try, a single normal log line makes get_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 | 🟠 Major

Delegate workspace file access to workspace_manager.

These methods bypass the isolated workspace entirely and operate on the original project_path instead. That breaks the PR's workspace-isolation contract, and because _project_path is not cleared on end_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 | 🟠 Major

Fail fast on unknown memory types in read paths too.

add_entry() validates memory_type, but get_entries() silently manufactures an empty MemoryFile for 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 | 🟠 Major

Reset 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_duration leak into the next session, so reusing one AgentSessionManager mixes 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 | 🟠 Major

Persist session timestamps as UTC Z timestamps.

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 | 🟠 Major

Persist memory timestamps in UTC with a Z suffix.

These models stamp entries/files with datetime.now() and export them with bare isoformat(), 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 | 🟠 Major

Expose a created session to the CLI before it becomes active.

After create_session(), _session_active is still False, so this sets cli.current_session = None. A follow-up run_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 | 🟡 Minor

Documentation 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, strengths

Apply 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 | 🟡 Minor

Typo: "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 | 🟡 Minor

Replace datetime.utcnow() with datetime.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 | 🟡 Minor

Modernize datetime serialization using Pydantic v2 patterns.

The json_encoders config using dict-style model_config is 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 in AgentSDKEncoder.default(), the json_encoders path is not invoked by Pydantic v2's model_dump_json().

Replace this with Pydantic v2's @field_serializer decorator or a custom type using PlainSerializer to properly define datetime serialization. The actual serialization currently relies on AgentSDKEncoder which 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 | 🟡 Minor

Use callable defaults for mutable JSON fields.

default=[] / default={} in ORM examples should be default=list / default=dict to 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 | 🟡 Minor

Remove 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 | 🟡 Minor

Top-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 | 🟡 Minor

WebSocket 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 | 🟡 Minor

Known 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 | 🟡 Minor

Filename typo: "FRONEND" should be "FRONTEND".

The file is named REVIEW_FRONEND.md but should be REVIEW_FRONTEND.md for 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 | 🟡 Minor

Fix 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 text or plaintext for ASCII diagrams, python for Python code, bash for 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 repeated get_settings monkeypatching into a shared fixture.

The current pattern is repeated in every test. A pytest fixture using monkeypatch will 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: Add text language 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 text for 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 with text.

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 ```text for 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 ```text for 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 text for 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., text or plaintext) 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 >= 0 will pass even if clear_memory(before_date=...) stops deleting old entries entirely. Because this test mutates the cached MemoryEntry instance, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6afec20 and 00bd70e.

📒 Files selected for processing (33)
  • AGENT_CAPABILITIES_ARCHITECTURE.md
  • BACKEND_ARCHITECTURE_BLUEPRINT.md
  • CHANGELOG.md
  • CLI_VS_SERVICE_DECISION.md
  • INTERACTIVE_AGENT_ARCHITECTURE.md
  • KNOWLEDGE_DB_ARCHITECTURE.md
  • MISSION_CONTROL_ARCHITECTURE.md
  • PHASE1_PROGRESS.md
  • REVIEW.md
  • REVIEW_FRONEND.md
  • TASK.md
  • UNIFIED_ARCHITECTURE_BLUEPRINT.md
  • package.json
  • pyproject.toml
  • src/ghostclaw/core/agent_sdk/__init__.py
  • src/ghostclaw/core/agent_sdk/agent_cli.py
  • src/ghostclaw/core/agent_sdk/agent_identity.py
  • src/ghostclaw/core/agent_sdk/agent_memory.py
  • src/ghostclaw/core/agent_sdk/agent_sdk.py
  • src/ghostclaw/core/agent_sdk/agent_session.py
  • src/ghostclaw/core/agent_sdk/agent_workspace.py
  • src/ghostclaw/core/agent_sdk/config.py
  • src/ghostclaw/core/agent_sdk/models.py
  • src/ghostclaw/core/agent_sdk/py.typed
  • src/ghostclaw/core/agent_sdk/serializers.py
  • src/ghostclaw/version.py
  • tests/unit/test_agent_cli.py
  • tests/unit/test_agent_identity.py
  • tests/unit/test_agent_memory.py
  • tests/unit/test_agent_sdk.py
  • tests/unit/test_agent_sdk_foundation.py
  • tests/unit/test_agent_session.py
  • tests/unit/test_agent_workspace.py

Comment thread BACKEND_ARCHITECTURE_BLUEPRINT.md
Comment thread src/ghostclaw/core/agent_sdk/agent_cli.py
Comment thread src/ghostclaw/core/agent_sdk/agent_cli.py
Comment thread src/ghostclaw/core/agent_sdk/agent_memory.py Outdated
Comment thread src/ghostclaw/core/agent_sdk/agent_sdk.py
Comment on lines +261 to +262
assert metadata.type == AgentType.CLI
assert metadata.type == AgentType.GENERALIST

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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}).

Comment on lines +264 to +276
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical syntax errors in test method.

This test method contains multiple syntax errors:

  • Line 267: metaname= instead of metadata = 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.

Comment on lines +278 to +291
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +293 to +314
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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).

Comment on lines +370 to +426
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"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Integration test has assertion errors and malformed code.

  • Line 390: Asserts restored.id == agent_id but agent_id is created at line 372 and never passed to AgentMetadata (line 375 creates it without id=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.

Suggested change
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"
Suggested change
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 | 🟡 Minor

ISO 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_list should use the workspace manager's list_files method 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. The AgentWorkspaceManager already provides a list_files() method that operates on its workspace_root with proper filtering and file metadata. Use workspace_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 | 🟡 Minor

Open 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 | 🟡 Minor

This example resolves an empty plan.

create_plan() is called without task_ids, so resolve_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 | 🟡 Minor

This assertion doesn't prove rotation happened.

events.jsonl already makes len(telemetry_files) >= 1 true, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 00bd70e and 0b6dd2b.

📒 Files selected for processing (12)
  • AGENT_CAPABILITIES_ARCHITECTURE.md
  • ORCHESTRATOR_TESTING_SUMMARY.md
  • docs/TASK_ORCHESTRATOR.md
  • src/ghostclaw/core/agent_sdk/__init__.py
  • src/ghostclaw/core/agent_sdk/agent_cli.py
  • src/ghostclaw/core/agent_sdk/agent_memory.py
  • src/ghostclaw/core/agent_sdk/agent_session.py
  • src/ghostclaw/core/agent_sdk/agent_task_orchestrator.py
  • src/ghostclaw/core/agent_sdk/agent_telemetry.py
  • tests/integration/test_agent_sdk_integration.py
  • tests/unit/test_agent_task_orchestrator.py
  • tests/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

Comment thread docs/TASK_ORCHESTRATOR.md
Comment on lines +16 to +18
- `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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment thread src/ghostclaw/core/agent_sdk/agent_cli.py
Comment on lines +356 to +387
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),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +470 to +503
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),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment thread src/ghostclaw/core/agent_sdk/agent_task_orchestrator.py
Comment on lines +75 to +95
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(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +355 to +360
# Flush adapter if available
if self.adapter:
try:
self.adapter.flush()
except Exception as e:
logger.error(f"Error flushing telemetry adapter: {e}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +388 to +395
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}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +38 to +48
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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.py

Repository: 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>
devin-ai-integration[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/ghostclaw/core/agent_sdk/agent_cli.py (3)

43-51: ⚠️ Potential issue | 🔴 Critical

Pass a UUID into AgentSessionManager, not the "default-agent" sentinel.

Line 43 still defaults agent_id to 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 | 🟠 Major

Return a clear pre-start error before calling memory manager methods.

After session create, get_memory_manager() can still be None. The current branches pass that None into the memory helpers, so the user gets an AttributeError instead of a clear “run session start first” 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 | 🟠 Major

Gate manager-dependent workspace commands on an initialized workspace manager.

After session create, get_workspace_manager() can still be None. branch, commit, and history then fail on AttributeError, and status is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0b6dd2b and 698af67.

📒 Files selected for processing (1)
  • src/ghostclaw/core/agent_sdk/agent_cli.py

Comment on lines +19 to +26
@dataclass
class CommandResult:
"""Result of a CLI command execution."""

success: bool
message: str
error: Optional[str] = None
data: Any = field(default=None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +532 to +541
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,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.
devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/ghostclaw/core/agent_sdk/agent_session.py (1)

44-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Timestamps still use local time across this module.

SessionAction.timestamp (line 44) and every datetime.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 with Z suffix. Switch to datetime.now(timezone.utc) and ensure ISO emission uses Z (e.g., replace("+00:00", "Z")) on all the .isoformat() call sites in export_session_data and _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 win

Bare except Exception blocks silently swallow real errors.

Every lifecycle method (start_session, pause_session, resume_session, end_session, load_session, cleanup_session) catches Exception and returns False/None with no logging. Failures like a SessionSummary validation error in end_session (which happens if _session_id is None) or a JSON decode failure in load_session will be invisible to callers and tests, making field debugging very hard.

At minimum, log the exception (e.g., via the standard logging module) before returning, and consider re-raising programmer errors (e.g., calling end_session before create_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_id is collision-prone — prefer uuid4.

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 to uuid4 for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 698af67 and 4c8edc5.

📒 Files selected for processing (6)
  • src/ghostclaw/core/agent_sdk/agent_cli.py
  • src/ghostclaw/core/agent_sdk/agent_memory.py
  • src/ghostclaw/core/agent_sdk/agent_sdk.py
  • src/ghostclaw/core/agent_sdk/agent_session.py
  • src/ghostclaw/core/agent_sdk/agent_workspace.py
  • src/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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

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.

Suggested change
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.

Comment thread src/ghostclaw/core/agent_sdk/agent_memory.py
Comment on lines +432 to +458
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

clear_memory has dead code, a lint failure, and an over-broad save condition — simplify.

Several problems compound here:

  • Line 453 assigns file_deleted and never uses it — Ruff F841 is failing CI.
  • original_count is only defined inside the if 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) is True whenever before_date is None, so the file is always rewritten in the "clear all" path even if memory.entries was already empty. For the before_date path, the len(memory.entries) == 0 clause causes a save even when nothing was deleted (e.g. an already-empty file).
  • When memory_type is provided as a single string, files_to_clear contains one element but no validation against MEMORY_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.

Comment on lines +451 to +492
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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 True

You'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.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 19 additional findings in Devin Review.

Open in Devin Review

Comment thread src/ghostclaw/core/agent_sdk/agent_task_orchestrator.py
if not line.strip():
continue

parts = line.split('|')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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 |.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

AgentStatus,
AgentType,
MessageRole,
SessionContext,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 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.
Open in Devin Review

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>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 19 additional findings in Devin Review.

Open in Devin Review

hash=parts[0][:7],
message=parts[1],
author=parts[2],
timestamp=datetime.fromisoformat(parts[3].replace(' ', 'T')),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 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.

Suggested change
timestamp=datetime.fromisoformat(parts[3].replace(' ', 'T')),
timestamp=datetime.fromisoformat(parts[3].strip().replace(' ', 'T', 1)),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +240 to +246
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant