Skip to content

feat: add session logging#1324

Closed
yumesha wants to merge 21 commits intoMoonshotAI:mainfrom
yumesha:feat/session-logging
Closed

feat: add session logging#1324
yumesha wants to merge 21 commits intoMoonshotAI:mainfrom
yumesha:feat/session-logging

Conversation

@yumesha
Copy link
Copy Markdown

@yumesha yumesha commented Mar 3, 2026

Summary

Add structured session logging for better debugging and audit trails.

Changes

  • New project_log.py with SessionLogger and SessionLogRecorder
  • Integrated logging across all entry points (shell, print, wire, multiagent)

Features

  • Session logging: Logs to ~/.kimi/sessions/<hash>/<session>/logs/
  • JSON Lines format: Easy to parse and query
  • Comprehensive events: Session start/end, prompts, thinking content, tool calls, approvals

Testing

  1. Run kimi in shell mode
  2. Have a conversation with tool calls
  3. Check ~/.kimi/sessions/<hash>/<session>/logs/ for log files
  4. Verify JSON Lines format contains expected events

Open with Devin

Use shutil.which() as the primary method for detecting bash location,
falling back to hardcoded paths only when necessary. This enables
proper shell detection on systems where bash is not located at
standard paths like /bin/bash.

Also updates tests to normalize bash paths for cross-platform
compatibility.
When using kimi -c "prompt" or kimi --command "prompt", the CLI now
automatically runs in print mode (non-interactive) similar to python -c
or claude -c behavior. This makes the -c flag work as expected for
single-shot command execution without requiring --print flag.
Patch StreamableHttpTransport to set terminate_on_close=False when
connecting to MCP servers via HTTP. This fixes the annoying
"Session termination failed: 404" warning that appears when using
servers like Supabase that don't support session DELETE requests.
Add SIGWINCH signal handler to detect terminal resize events and
force a recompose of the Live display. This fixes the issue where
text content would get stuck at a narrow width when the terminal
window was resized smaller, and not expand when resized back larger.
Clear Rich's cached console dimensions (_width, _height) and Live's
render shape when SIGWINCH is received. This fixes the empty gap
issue when resizing terminals in Hyprland and other WMs.
- Add ClipboardVideo class and grab_video_from_clipboard() in clipboard.py

- Add video support to AttachmentCache with store_video_reference()

- Add _try_paste_video() method in prompt.py for Ctrl-V handling

- Add _extract_video_paths() and _build_content_parts() in print/__init__.py

- Export VIDEO_EXTENSIONS constant from file utils

- Update documentation for video clipboard paste

- Add tests for video attachment handling
Remove device fingerprinting data from HTTP headers:
- Device name, model, OS version now return 'anonymous'/'unknown'
- Device ID is now a static zeroed UUID
- Default aiohttp User-Agent replaced with minimal KimiCLI/<version>

Also cleaned up unused functions and imports in oauth module.

Bumps version to 1.15.0
## Summary

Add structured session logging for better debugging and audit trails.

## Changes

### kimi_cli/
- `project_log.py`: NEW - Core logging module with SessionLogger and SessionLogRecorder
- `app.py`: Create SessionLogger and pass to run_soul
- `soul/__init__.py`: Integrate session logging for events, prompts, tool calls
- `tools/multiagent/task.py`: Pass logger to subagents
- `ui/print/__init__.py`: Logger integration
- `ui/shell/__init__.py`: Logger integration
- `wire/server.py`: Logger integration

## Features

- **Session logging**: Logs to `~/.kimi/sessions/<hash>/<session>/logs/` in JSON Lines format
- **Comprehensive events**: Session start/end, prompts, thinking, tool calls, approvals
@yumesha yumesha closed this Mar 3, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47941d37f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +157 to +159
session_recorder = SessionLogRecorder(session_logger)
session_recorder.start()
await session_logger.log_session_start()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Feed wire events into the session log recorder

run_soul starts a SessionLogRecorder here, but nothing ever enqueues messages into it (the existing wire_send path still only calls wire.soul_side.send). That means this feature only writes session_start.jsonl/session_end.jsonl, while event logs like user_prompt_submit.jsonl, thinking.jsonl, and tool_calls.jsonl never get populated. Please wire the recorder to actual wire traffic (e.g., by forwarding each emitted WireMessage or subscribing the recorder to a wire queue) before shutdown.

Useful? React with 👍 / 👎.

Comment on lines +394 to +396
for arg in sys.argv[1:]:
if arg in ("-c", "--command"):
command_mode = True
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize attached --command values when enabling print mode

This detection only matches raw argv tokens equal to -c or --command, so valid option forms with attached values (such as --command=... or -c...) are not treated as command mode. In those cases prompt is populated but command_mode stays false, so the run falls back to shell UI instead of the auto-print behavior added by this change. Use parsed option state (or handle attached forms explicitly) rather than exact-token matching.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +133 to +137
session_logger = SessionLogger(
work_dir=Path(str(self._runtime.session.work_dir)),
session_id=self._runtime.session.id,
session_dir=self._runtime.session.dir,
)
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.

🔴 Task tool references non-existent self._runtime attribute, causing AttributeError

The newly added session logging code in Task._run_subagent references self._runtime at lines 134-136, but self._runtime is never assigned anywhere. The Task.__init__ (src/kimi_cli/tools/multiagent/task.py:57-70) only stores self._labor_market = runtime.labor_market and self._session = runtime.session, and the parent class CallableTool2 has no _runtime attribute either.

Root Cause and Impact

The code at line 134 does self._runtime.session.work_dir, but self._runtime doesn't exist. The existing code already has the session stored as self._session (line 70: self._session = runtime.session), so the correct references should be self._session.work_dir, self._session.id, and self._session.dir.

This will raise an AttributeError every time a subagent is spawned via the Task tool, causing the subagent call to fail with "Failed to run subagent" error.

Suggested change
session_logger = SessionLogger(
work_dir=Path(str(self._runtime.session.work_dir)),
session_id=self._runtime.session.id,
session_dir=self._runtime.session.dir,
)
session_logger = SessionLogger(
work_dir=Path(str(self._session.work_dir)),
session_id=self._session.id,
session_dir=self._session.dir,
)
Open in Devin Review

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

Comment on lines +191 to +193
except Exception:
end_reason = "error"
raise
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.

🟡 Cancelled run logs end_reason as "error" instead of "cancelled"

In run_soul, when a run is cancelled, end_reason is set to "cancelled" on line 180, and then RunCancelled is raised on line 184. However, RunCancelled extends Exception (src/kimi_cli/soul/__init__.py:118), so it is caught by the except Exception: block on line 191, which overwrites end_reason to "error" before re-raising.

Detailed Explanation

The flow is:

  1. end_reason = "cancelled" (line 180)
  2. raise RunCancelled from None (line 184)
  3. except Exception: catches RunCancelled (line 191)
  4. end_reason = "error" (line 192) — overwrites the correct value
  5. raise re-raises RunCancelled (line 193)
  6. finally: logs end_reason as "error" instead of "cancelled" (line 197)

The same issue applies to MaxStepsReached and LLMNotSet and other expected exceptions that go through line 190's soul_task.result() — they are all logged as "error" rather than being distinguished.

To fix, the except Exception handler should preserve the already-set end_reason (e.g., only overwrite if it's still "completed").

Suggested change
except Exception:
end_reason = "error"
raise
except RunCancelled:
end_reason = "cancelled"
raise
except Exception:
end_reason = "error"
raise
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