Skip to content

Conversation

@yaya1738
Copy link

@yaya1738 yaya1738 commented Dec 4, 2025

Summary

Implements FUSE-based virtual filesystem providing file-like interface to LLM APIs. Enables shell scripts and any Unix program to use LLMs.

Features:

  • File-based interface: write prompts, read responses
  • Claude API client with mock fallback for testing
  • Session management with conversation history
  • JSON configuration for model parameters
  • Metrics tracking for API calls and token usage

Usage:

# Mount the filesystem
python llm_device.py mount /mnt/llm

# Simple query
echo "What is 2+2?" > /mnt/llm/claude/prompt
cat /mnt/llm/claude/response

# Stateful sessions
mkdir /mnt/llm/sessions/my-project
echo "What is Python?" > /mnt/llm/sessions/my-project/prompt
cat /mnt/llm/sessions/my-project/response

Directory Structure:

/mnt/llm/
├── claude/
│   ├── prompt      # Write prompts here
│   ├── response    # Read responses
│   ├── config      # JSON configuration
│   └── metrics     # Usage stats
├── sessions/       # Stateful conversations
└── status          # System status

Test Plan

  • 20/20 unit tests passing
  • Tested mock client responses
  • Tested session creation and history
  • Tested FUSE operations (read, write, readdir, getattr)
  • Tested configuration updates

Files Added

  • cortex/kernel_features/llm_device/__init__.py
  • cortex/kernel_features/llm_device/llm_device.py (574 lines)
  • cortex/kernel_features/llm_device/test_llm_device.py (176 lines)
  • cortex/kernel_features/llm_device/README.md

Closes #223

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced a file-based LLM interface supporting prompt submission, response retrieval, and session management.
    • Added support for both Claude API and mock client modes for flexibility.
  • Documentation

    • Added comprehensive guide with setup instructions, usage examples, and requirements.
  • Tests

    • Added full test suite covering client operations, session management, and device functionality.

✏️ Tip: You can customize this high-level summary in your review settings.

- FUSE filesystem providing file-like interface to LLM APIs
- Write to prompt, read from response - any Unix program can use LLMs
- Session management with conversation history
- Claude API client with mock fallback for testing
- Configuration and metrics via JSON files
- 20/20 tests passing

Bounty: cortexlinux#223
Copilot AI review requested due to automatic review settings December 4, 2025 05:40
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Warning

Rate limit exceeded

@yaya1738 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 16 minutes and 48 seconds before requesting another review.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1a34c38 and 1b16b02.

📒 Files selected for processing (1)
  • cortex/kernel_features/llm_device/__init__.py (1 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Introduces a FUSE-based virtual device /dev/llm providing a file-like interface to LLM operations. Implements LLMDevice filesystem with MockLLMClient and ClaudeLLMClient wrappers, Session-based context management, and a CLI entry point for mounting and testing. Includes comprehensive test coverage and documentation.

Changes

Cohort / File(s) Change Summary
Documentation
cortex/kernel_features/llm_device/README.md
New README documenting FUSE-based /dev/llm virtual device: features, directory layout, usage examples, testing commands, and requirements (fusepy, anthropic, FUSE).
Package Initialization
cortex/kernel_features/llm_device/__init__.py
New package initializer re-exporting four public symbols: LLMDevice, MockLLMClient, ClaudeLLMClient, and Session from the internal module.
Core Implementation
cortex/kernel_features/llm_device/llm_device.py
New FUSE filesystem implementation with MockLLMClient and ClaudeLLMClient for LLM operations, Session dataclass for conversation state, LLMDevice class handling FUSE operations (getattr, readdir, read, write, open, create, mkdir, unlink, rmdir, truncate), and CLI entry point supporting mount and test modes.
Test Suite
cortex/kernel_features/llm_device/test_llm_device.py
Comprehensive unittest module covering MockLLMClient behavior, Session management, LLMDevice filesystem operations, prompt/response IO, config management, metrics exposure, and per-session file operations.

Sequence Diagram

sequenceDiagram
    participant User
    participant FUSE as FUSE Layer
    participant LLMDev as LLMDevice
    participant Session
    participant Client as LLM Client
    participant API as Anthropic API

    rect rgb(200, 230, 255)
    Note over User,API: Writing a prompt to a regular session file
    User->>FUSE: write("/sessions/my-project/prompt", "What is AI?")
    FUSE->>LLMDev: write() handler
    LLMDev->>Session: add_exchange() / build context
    Session-->>LLMDev: context with last 5 exchanges
    LLMDev->>Client: query_llm(context_prompt)
    alt Using Claude Client
        Client->>API: POST /messages (with context)
        API-->>Client: response_text
    else Using Mock Client
        Client-->>Client: deterministic mock response
    end
    Client-->>LLMDev: response_text, tokens_used
    LLMDev->>Session: store exchange + response
    FUSE-->>User: write() completes
    end

    rect rgb(230, 255, 200)
    Note over User,LLMDev: Reading the response file
    User->>FUSE: read("/sessions/my-project/response")
    FUSE->>LLMDev: read() handler
    LLMDev->>Session: retrieve stored response
    Session-->>LLMDev: response_text
    FUSE-->>User: response_text
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

  • FUSE operation correctness: All file system operations (getattr, readdir, read, write, open, create, mkdir, unlink, rmdir, truncate) require careful verification against FUSE semantics and edge cases.
  • Thread-safety implementation: Lock-based synchronization for concurrent access requires review to ensure proper protection and no deadlock scenarios.
  • LLM client integration: Both MockLLMClient and ClaudeLLMClient implementations need validation for API call handling, error fallback logic, and metrics tracking.
  • Session context management: The logic for building contextual prompts from history (last 5 exchanges) and managing per-session state needs correctness verification.
  • Concurrency with external API calls: Ensure proper handling of blocking API calls within FUSE handlers and any potential race conditions.

Possibly related PRs

Suggested labels

enhancement, kernel-features

Poem

🐰 A filesystem springs from the earth so deep,
Where prompts and responses dance while rabbits sleep,
Through FUSE-magic tunnels, the LLMs now dwell,
In /dev/llm burrows, where visions compel—
File-based wisdom, no APIs in sight! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a FUSE-based /dev/llm virtual device feature. It directly references issue #223 and accurately reflects the primary objective of the pull request.
Description check ✅ Passed The PR description covers all essential sections: summary of the FUSE filesystem implementation, features, usage examples with code blocks, directory structure, test plan with passing results, and files added. It follows the repository template requirements.
Linked Issues check ✅ Passed The implementation fulfills all stated objectives: FUSE filesystem with multiple model endpoints [#223], session management with conversation history [#223], Anthropic API integration with mock fallback [#223], JSON configuration support [#223], metrics tracking [#223], and comprehensive testing [#223].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the /dev/llm virtual device feature. The pull request adds the core LLM device implementation, package initialization, comprehensive tests, and documentation—all aligned with issue #223 objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (8)
cortex/kernel_features/llm_device/README.md (1)

22-36: Add a language to the directory-structure code fence (markdownlint MD040).

To satisfy MD040 and make renderers happier, tag the directory tree block with a language, e.g.:

-```
+```text
 /mnt/llm/
 ├── claude/              # Claude Sonnet
 ...
-```
+```
cortex/kernel_features/llm_device/__init__.py (1)

9-14: Optionally sort __all__ to satisfy Ruff RUF022.

Everything exported here is correct; to make Ruff happy you can alphabetize __all__:

-__all__ = [
-    "LLMDevice",
-    "MockLLMClient",
-    "ClaudeLLMClient",
-    "Session",
-]
+__all__ = [
+    "ClaudeLLMClient",
+    "LLMDevice",
+    "MockLLMClient",
+    "Session",
+]
cortex/kernel_features/llm_device/test_llm_device.py (1)

1-10: Make test imports package-safe and consider adjusting the shebang.

Two minor points here:

  • The relative import from llm_device import ... only works reliably when running python test_llm_device.py from within this directory. For running under pytest or unittest from the repo root, consider importing via the package instead:
-from llm_device import LLMDevice, MockLLMClient, Session
+from cortex.kernel_features.llm_device import LLMDevice, MockLLMClient, Session

You can then run tests as a module (e.g., python -m cortex.kernel_features.llm_device.test_llm_device).

  • Ruff flags the shebang (EXE001) because the file is not executable in the repo. Either make the file executable if you intend direct execution, or drop the shebang and rely on python -m / test runners.
cortex/kernel_features/llm_device/llm_device.py (5)

49-77: Tidy MockLLMClient.complete signature and f-string.

The mock client works functionally, but a couple of small cleanups will quiet linters and clarify intent:

  • config is unused but present for API compatibility; rename to _config (and update type hint) so Ruff doesn’t flag it.
  • The time branch uses an f-string with no placeholders.

For example:

-    def complete(self, prompt: str, config: dict = None) -> str:
+    def complete(self, prompt: str, _config: dict | None = None) -> str:
@@
-        elif "what" in prompt.lower() and "time" in prompt.lower():
-            return f"I don't have access to real-time data, but I can help with other questions."
+        elif "what" in prompt.lower() and "time" in prompt.lower():
+            return "I don't have access to real-time data, but I can help with other questions."

This keeps the public API shape while addressing ARG002/RUF013/F541.


208-213: config["model"] is never honored when calling the client.

self.config includes a "model" key and is exposed via /claude/config, but ClaudeLLMClient.complete always uses self.model when calling messages.create. That means changing "model" in the config file has no effect on actual calls.

If you intend the JSON config to control the model too, consider:

-        max_tokens = config.get("max_tokens", 1024)
-        temperature = config.get("temperature", 0.7)
+        max_tokens = config.get("max_tokens", 1024)
+        temperature = config.get("temperature", 0.7)
+        model = config.get("model", self.model)
@@
-            response = self.client.messages.create(
-                model=self.model,
+            response = self.client.messages.create(
+                model=model,
                 max_tokens=max_tokens,
                 temperature=temperature,
                 messages=[{"role": "user", "content": prompt}]
             )

Optionally also report the effective model in get_metrics().

Also applies to: 442-447


221-259: Factor repeated path literals into module-level constants.

Paths like "/claude/prompt", "/claude/response", "/claude/config", "/claude/metrics", "/sessions", and "/sessions/" appear many times across _init_structure, getattr, readdir, read, write, truncate, and session helpers. Sonar is flagging these duplications.

Defining shared constants will:

  • Reduce the chance of typos in one location,
  • Make directory layout changes easier, and
  • Satisfy the Sonar “duplicate literal” checks.

For example:

+CLAUDE_DIR = "/claude"
+SESSIONS_DIR = "/sessions"
+STATUS_PATH = "/status"
+CLAUDE_PROMPT = f"{CLAUDE_DIR}/prompt"
+CLAUDE_RESPONSE = f"{CLAUDE_DIR}/response"
+CLAUDE_CONFIG = f"{CLAUDE_DIR}/config"
+CLAUDE_METRICS = f"{CLAUDE_DIR}/metrics"
+
@@
-        dirs = ["/", "/claude", "/sessions"]
+        dirs = ["/", CLAUDE_DIR, SESSIONS_DIR]
@@
-        files = [
-            "/status",
-            "/claude/prompt",
-            "/claude/response",
-            "/claude/config",
-            "/claude/metrics"
-        ]
+        files = [STATUS_PATH, CLAUDE_PROMPT, CLAUDE_RESPONSE, CLAUDE_CONFIG, CLAUDE_METRICS]

and then reuse these names throughout getattr, readdir, read, write, etc.

Also applies to: 264-329, 336-377, 424-455, 457-507


1-1: Optional: Align shebang usage with how this module is invoked.

Ruff flags the shebang (EXE001) because this file is not typically executed as an executable script in the repo (it’s imported and used via python -m / package imports).

Two options:

  • Keep the shebang and make the file executable if you expect ./llm_device.py usage, or
  • Drop the shebang and rely on python -m cortex.kernel_features.llm_device.llm_device / main() entry points.

Either way is fine; this is mainly about silencing the linter and clarifying the intended invocation pattern.


264-377: Prefix unused FUSE method arguments with underscore to satisfy ARG002 linting.

FUSE method signatures like getattr(self, path, fh=None), readdir(self, path, fh), read(self, path, size, offset, fh), write(self, path, data, offset, fh), and truncate(self, path, length, fh=None) are API-mandated and cannot be changed. When parameters go unused, Ruff's ARG002 rule flags them as unused arguments.

Rename unused parameters with a leading underscore (e.g., _fh, _offset) to signal intentional non-use and eliminate the linting noise. Alternatively, add # noqa: ARG002 on specific methods if renaming is not preferred. This applies to all FUSE operation methods in this class.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da3e635 and 1a34c38.

📒 Files selected for processing (4)
  • cortex/kernel_features/llm_device/README.md (1 hunks)
  • cortex/kernel_features/llm_device/__init__.py (1 hunks)
  • cortex/kernel_features/llm_device/llm_device.py (1 hunks)
  • cortex/kernel_features/llm_device/test_llm_device.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
cortex/kernel_features/llm_device/__init__.py (1)
cortex/kernel_features/llm_device/llm_device.py (4)
  • LLMDevice (176-507)
  • MockLLMClient (49-77)
  • ClaudeLLMClient (80-127)
  • Session (135-169)
cortex/kernel_features/llm_device/llm_device.py (2)
cortex/kernel_features/llm_device.py (2)
  • FuseOSError (22-23)
  • Operations (24-24)
logging_system.py (1)
  • debug (207-209)
cortex/kernel_features/llm_device/test_llm_device.py (1)
cortex/kernel_features/llm_device/llm_device.py (16)
  • LLMDevice (176-507)
  • MockLLMClient (49-77)
  • Session (135-169)
  • complete (57-70)
  • complete (99-118)
  • get_metrics (72-77)
  • get_metrics (120-127)
  • add_exchange (142-147)
  • get_history (149-156)
  • get_context_prompt (158-169)
  • read (331-334)
  • write (336-366)
  • readdir (312-329)
  • getattr (264-310)
  • _write_session_file (477-507)
  • _get_session_file_content (457-475)
🪛 GitHub Check: SonarCloud Code Analysis
cortex/kernel_features/llm_device/llm_device.py

[failure] 244-244: Define a constant instead of duplicating this literal "/claude/config" 4 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPc&open=AZrn4RvolL331w9I4oPc&pullRequest=233


[failure] 243-243: Define a constant instead of duplicating this literal "/claude/response" 3 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPY&open=AZrn4RvolL331w9I4oPY&pullRequest=233


[warning] 68-68: Add replacement fields or use a normal string instead of an f-string.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPf&open=AZrn4RvolL331w9I4oPf&pullRequest=233


[failure] 242-242: Define a constant instead of duplicating this literal "/claude/prompt" 5 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPb&open=AZrn4RvolL331w9I4oPb&pullRequest=233


[failure] 245-245: Define a constant instead of duplicating this literal "/claude/metrics" 3 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPa&open=AZrn4RvolL331w9I4oPa&pullRequest=233


[warning] 57-57: Remove the unused function parameter "config".

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPe&open=AZrn4RvolL331w9I4oPe&pullRequest=233


[failure] 267-267: Define a constant instead of duplicating this literal "/sessions/" 8 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPd&open=AZrn4RvolL331w9I4oPd&pullRequest=233


[failure] 368-368: Refactor this method to not always return the same value.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPg&open=AZrn4RvolL331w9I4oPg&pullRequest=233


[failure] 226-226: Define a constant instead of duplicating this literal "/sessions" 3 times.

See more on https://sonarcloud.io/project/issues?id=cortexlinux_cortex&issues=AZrn4RvolL331w9I4oPZ&open=AZrn4RvolL331w9I4oPZ&pullRequest=233

🪛 markdownlint-cli2 (0.18.1)
cortex/kernel_features/llm_device/README.md

22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.7)
cortex/kernel_features/llm_device/__init__.py

9-14: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

cortex/kernel_features/llm_device/llm_device.py

1-1: Shebang is present but file is not executable

(EXE001)


57-57: Unused method argument: config

(ARG002)


57-57: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


68-68: f-string without any placeholders

Remove extraneous f prefix

(F541)


83-83: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


99-99: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


117-117: Do not catch blind exception: Exception

(BLE001)


264-264: Unused method argument: fh

(ARG002)


312-312: Unused method argument: fh

(ARG002)


331-331: Unused method argument: fh

(ARG002)


336-336: Unused method argument: offset

(ARG002)


336-336: Unused method argument: fh

(ARG002)


368-368: Unused method argument: fh

(ARG002)


379-379: Unused method argument: path

(ARG002)


379-379: Unused method argument: flags

(ARG002)


383-383: Unused method argument: mode

(ARG002)


383-383: Unused method argument: fi

(ARG002)


394-394: Unused method argument: mode

(ARG002)


405-405: Unused method argument: path

(ARG002)

cortex/kernel_features/llm_device/test_llm_device.py

1-1: Shebang is present but file is not executable

(EXE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent

Comment on lines +99 to +119
def complete(self, prompt: str, config: dict = None) -> str:
if not self.client:
return "[Error] Anthropic client not available. Set ANTHROPIC_API_KEY or use mock mode."

config = config or {}
max_tokens = config.get("max_tokens", 1024)
temperature = config.get("temperature", 0.7)

try:
response = self.client.messages.create(
model=self.model,
max_tokens=max_tokens,
temperature=temperature,
messages=[{"role": "user", "content": prompt}]
)
self.call_count += 1
self.total_tokens += response.usage.input_tokens + response.usage.output_tokens
return response.content[0].text
except Exception as e:
return f"[Error] API call failed: {e}"

Copy link
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:

# Check Anthropic imports and usage in the codebase
rg "anthropic" --type python -i

Repository: cortexlinux/cortex

Length of output: 180


🌐 Web query:

Anthropic Python SDK exception types and error handling

💡 Result:

Short summary — Anthropic Python SDK exception types and handling

  • Base classes:

    • anthropic.APIError — base for all SDK errors. [1][2]
    • anthropic.APIConnectionError — network/connection/timeouts (wraps underlying httpx errors). [1][2]
    • anthropic.APIStatusError — non-2xx HTTP responses; exposes .status_code and .response. [1][2]
  • HTTP-status specific subclasses (raise as APIStatusError subclasses):

    • 400 → BadRequestError
    • 401 → AuthenticationError
    • 403 → PermissionDeniedError
    • 404 → NotFoundError
    • 422 → UnprocessableEntityError
    • 429 → RateLimitError
    • =500 → InternalServerError. [1][2]

  • Retry & timeout behavior:

    • SDK retries certain errors by default (2 retries): connection errors, 408, 409, 429, >=500. Configurable via max_retries. Default request timeout provided (configurable). [1][2]
  • Debugging helpers:

    • Responses include a _request_id from the request-id header for logging/troubleshooting. [1][2]
  • Typical handling pattern (concise):

    • catch APIConnectionError for network issues, RateLimitError for 429s, catch APIStatusError for other non-success statuses, or catch APIError as a fallback. [1][2]

Sources:
[1] anthropics/anthropic-sdk-python (GitHub) — Handling errors section.
[2] Anthropic Python SDK docs / third‑party docs summarizing the SDK error types.


Narrow the broad except Exception in ClaudeLLMClient.complete to specific Anthropic SDK exceptions.

Catching a bare Exception hides programmer errors (e.g., attribute errors, type errors) by converting them into user-visible error strings, making debugging harder.

The Anthropic Python SDK provides a clear exception hierarchy:

  • anthropic.APIError — base exception for all SDK errors
  • anthropic.APIConnectionError — network/connection/timeout issues
  • anthropic.APIStatusError — non-2xx HTTP responses (with specific subclasses: BadRequestError, AuthenticationError, RateLimitError, InternalServerError, etc.)

Recommended approach:

from anthropic import APIError

try:
    response = self.client.messages.create(...)
    ...
    return response.content[0].text
except APIError as e:
    return f"[Error] API call failed: {e}"

Or catch more granularly for different behaviors (e.g., APIConnectionError for retries, RateLimitError for rate limiting).

🧰 Tools
🪛 Ruff (0.14.7)

99-99: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


117-117: Do not catch blind exception: Exception

(BLE001)

Comment on lines +514 to +569
def main():
import argparse

parser = argparse.ArgumentParser(description="/dev/llm Virtual Device")
parser.add_argument("command", choices=["mount", "test"], help="Command to run")
parser.add_argument("mountpoint", nargs="?", default="/mnt/llm", help="Mount point")
parser.add_argument("--mock", action="store_true", help="Use mock client (no API)")
parser.add_argument("--foreground", "-f", action="store_true", help="Run in foreground")
parser.add_argument("--debug", "-d", action="store_true", help="Enable debug output")

args = parser.parse_args()

if args.command == "test":
# Quick test without mounting
print("Testing LLM Device...")
device = LLMDevice(use_mock=True)

# Simulate write to prompt
prompt = b"What is 2+2?"
device.write("/claude/prompt", prompt, 0, None)

# Read response
response = device.read("/claude/response", 4096, 0, None)
print(f"Prompt: {prompt.decode()}")
print(f"Response: {response.decode()}")

# Check metrics
metrics = device.read("/claude/metrics", 4096, 0, None)
print(f"Metrics: {metrics.decode()}")

print("\nTest passed!")
return

# Mount filesystem
mountpoint = Path(args.mountpoint)

if not mountpoint.exists():
mountpoint.mkdir(parents=True)

print(f"Mounting /dev/llm at {mountpoint}")
print(f"Mock mode: {args.mock}")
print("Usage:")
print(f" echo 'Hello' > {mountpoint}/claude/prompt")
print(f" cat {mountpoint}/claude/response")
print("\nPress Ctrl+C to unmount")

device = LLMDevice(use_mock=args.mock)

FUSE(
device,
str(mountpoint),
foreground=args.foreground or True,
allow_other=False,
nothreads=False,
debug=args.debug
)
Copy link
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

🌐 Web query:

fusepy FUSE constructor foreground parameter documentation

💡 Result:

In fusepy, the FUSE constructor's foreground parameter (foreground=True) tells FUSE not to daemonize — it runs the mount process in the foreground (blocking until unmounted), which is useful for debugging. Examples and docs show passing foreground=True to keep the process in the foreground. [1][2]

Sources:

  • example/docs showing foreground option blocks instead of spawning background process. [1]
  • community examples/usage showing FUSE(..., foreground=True). [2]

Fix foreground parameter being hardcoded to True: CLI flag is ineffective.

In the FUSE call:

FUSE(
    device,
    str(mountpoint),
    foreground=args.foreground or True,
    ...
)

The expression args.foreground or True always evaluates to True (since args.foreground defaults to False when the flag is not provided). This means:

  • The filesystem always runs in the foreground regardless of the flag,
  • The --foreground/-f flag has no effect,
  • There's no way to run in the background.

Remove the or True fallback to pass the flag through correctly:

-    FUSE(
-        device,
-        str(mountpoint),
-        foreground=args.foreground or True,
-        allow_other=False,
-        nothreads=False,
-        debug=args.debug
-    )
+    FUSE(
+        device,
+        str(mountpoint),
+        foreground=args.foreground,
+        allow_other=False,
+        nothreads=False,
+        debug=args.debug,
+    )

If you want foreground to be the default instead, adjust the argument default or flip the semantics (e.g., add a --background flag).

🤖 Prompt for AI Agents
In cortex/kernel_features/llm_device/llm_device.py around lines 514 to 569, the
FUSE call uses "foreground=args.foreground or True" which forces foreground to
True so the CLI flag is ignored; change it to pass the flag value directly
(foreground=args.foreground) or adjust the argparse default/flag semantics if
you want foreground=True by default (e.g., set
parser.add_argument("--foreground", "-f", action="store_true", default=True) or
introduce a --background flag) so the --foreground/-f option actually controls
running in foreground vs background.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a FUSE-based virtual filesystem that provides a file-like interface to LLM APIs, specifically targeting Claude. The implementation enables shell scripts and Unix programs to interact with LLMs through standard file operations.

Key changes:

  • Adds FUSE filesystem with Claude API client and mock fallback for testing
  • Implements session management for stateful conversations with context history
  • Provides JSON-based configuration and metrics tracking for API usage

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 22 comments.

File Description
cortex/kernel_features/llm_device/llm_device.py Core FUSE filesystem implementation with LLM clients, session management, and file operations
cortex/kernel_features/llm_device/test_llm_device.py Unit tests covering mock client, sessions, FUSE operations, and session files
cortex/kernel_features/llm_device/__init__.py Module initialization exporting main classes
cortex/kernel_features/llm_device/README.md Documentation covering features, usage examples, and requirements

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +350 to +352
try:
new_config = json.loads(data.decode("utf-8"))
self.config.update(new_config)
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Configuration updates via JSON don't validate the values being set. Malicious users could inject arbitrary configuration values (e.g., negative max_tokens, invalid temperature values > 1.0 or < 0.0, or unexpected keys). Consider validating config keys and value ranges before updating.

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +118
except Exception as e:
return f"[Error] API call failed: {e}"
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The generic except Exception as e catches all exceptions, including system errors and keyboard interrupts. This makes debugging difficult and could hide serious issues. Consider catching specific exceptions (e.g., anthropic.APIError, anthropic.APIConnectionError) and letting critical exceptions propagate.

Copilot uses AI. Check for mistakes.
session = self.sessions[session_name]

if filename == "prompt":
prompt = data.decode("utf-8").strip()
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The data.decode("utf-8") call can raise a UnicodeDecodeError if the data contains invalid UTF-8 bytes. This should be wrapped in a try-except block or use errors='replace' parameter to handle invalid input gracefully.

Copilot uses AI. Check for mistakes.
Comment on lines +314 to +334
entries = [".", ".."]

if path == "/":
entries.extend(["claude", "sessions", "status"])
elif path == "/claude":
entries.extend(["prompt", "response", "config", "metrics"])
elif path == "/sessions":
entries.extend(self.sessions.keys())
elif path.startswith("/sessions/"):
parts = path.split("/")
if len(parts) == 3:
session_name = parts[2]
if session_name in self.sessions:
entries.extend(["prompt", "response", "history", "config"])

return entries

def read(self, path, size, offset, fh):
"""Read file content."""
content = self._get_file_content(path)
return content[offset:offset + size]
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The lock is only used in the write method, but read, readdir, mkdir, rmdir, and other methods access shared state (e.g., self.sessions, self.responses, self.prompts) without locking. This creates potential race conditions when multiple threads access the filesystem concurrently. Consider protecting all shared state access with the lock.

Suggested change
entries = [".", ".."]
if path == "/":
entries.extend(["claude", "sessions", "status"])
elif path == "/claude":
entries.extend(["prompt", "response", "config", "metrics"])
elif path == "/sessions":
entries.extend(self.sessions.keys())
elif path.startswith("/sessions/"):
parts = path.split("/")
if len(parts) == 3:
session_name = parts[2]
if session_name in self.sessions:
entries.extend(["prompt", "response", "history", "config"])
return entries
def read(self, path, size, offset, fh):
"""Read file content."""
content = self._get_file_content(path)
return content[offset:offset + size]
with self.lock:
entries = [".", ".."]
if path == "/":
entries.extend(["claude", "sessions", "status"])
elif path == "/claude":
entries.extend(["prompt", "response", "config", "metrics"])
elif path == "/sessions":
entries.extend(self.sessions.keys())
elif path.startswith("/sessions/"):
parts = path.split("/")
if len(parts) == 3:
session_name = parts[2]
if session_name in self.sessions:
entries.extend(["prompt", "response", "history", "config"])
return entries
def read(self, path, size, offset, fh):
"""Read file content."""
with self.lock:
content = self._get_file_content(path)
return content[offset:offset + size]

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +172
class TestLLMDevice(unittest.TestCase):
"""Test FUSE filesystem operations."""

def setUp(self):
self.device = LLMDevice(use_mock=True)

def test_read_status(self):
content = self.device.read("/status", 4096, 0, None)
status = json.loads(content.decode())
self.assertEqual(status["status"], "running")
self.assertTrue(status["mock_mode"])

def test_write_prompt_read_response(self):
# Write prompt
prompt = b"What is 2+2?"
self.device.write("/claude/prompt", prompt, 0, None)

# Read response
response = self.device.read("/claude/response", 4096, 0, None)
self.assertEqual(response.decode(), "4")

def test_read_config(self):
content = self.device.read("/claude/config", 4096, 0, None)
config = json.loads(content.decode())
self.assertIn("max_tokens", config)
self.assertIn("temperature", config)

def test_write_config(self):
new_config = json.dumps({"max_tokens": 2048}).encode()
self.device.write("/claude/config", new_config, 0, None)
self.assertEqual(self.device.config["max_tokens"], 2048)

def test_read_metrics(self):
# Generate some activity
self.device.write("/claude/prompt", b"Test", 0, None)

content = self.device.read("/claude/metrics", 4096, 0, None)
metrics = json.loads(content.decode())
self.assertGreater(metrics["calls"], 0)

def test_readdir_root(self):
entries = self.device.readdir("/", None)
self.assertIn("claude", entries)
self.assertIn("sessions", entries)
self.assertIn("status", entries)

def test_readdir_claude(self):
entries = self.device.readdir("/claude", None)
self.assertIn("prompt", entries)
self.assertIn("response", entries)
self.assertIn("config", entries)
self.assertIn("metrics", entries)

def test_getattr_file(self):
attrs = self.device.getattr("/claude/prompt")
self.assertTrue(attrs["st_mode"] & 0o100000) # Regular file

def test_getattr_directory(self):
attrs = self.device.getattr("/claude")
self.assertTrue(attrs["st_mode"] & 0o40000) # Directory


class TestSessionFiles(unittest.TestCase):
"""Test session file operations."""

def setUp(self):
self.device = LLMDevice(use_mock=True)

def test_create_session(self):
self.device.mkdir("/sessions/my-project", 0o755)
self.assertIn("my-project", self.device.sessions)

def test_session_prompt_response(self):
# Create session
self.device.mkdir("/sessions/test", 0o755)

# Write prompt
self.device._write_session_file("/sessions/test/prompt", b"What is 2+2?")

# Read response
content = self.device._get_session_file_content("test", "response")
self.assertEqual(content.decode(), "4")

def test_session_history(self):
self.device.mkdir("/sessions/test", 0o755)
self.device._write_session_file("/sessions/test/prompt", b"Q1")
self.device._write_session_file("/sessions/test/prompt", b"Q2")

history = self.device._get_session_file_content("test", "history")
self.assertIn(b"Q1", history)
self.assertIn(b"Q2", history)

def test_readdir_sessions(self):
self.device.mkdir("/sessions/project-a", 0o755)
self.device.mkdir("/sessions/project-b", 0o755)

entries = self.device.readdir("/sessions", None)
self.assertIn("project-a", entries)
self.assertIn("project-b", entries)

def test_readdir_session_files(self):
self.device.mkdir("/sessions/test", 0o755)

entries = self.device.readdir("/sessions/test", None)
self.assertIn("prompt", entries)
self.assertIn("response", entries)
self.assertIn("history", entries)
self.assertIn("config", entries)

Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

There are no tests for error conditions such as accessing non-existent files/paths (which should raise FuseOSError with errno.ENOENT) or writing to read-only files. Consider adding negative test cases to ensure proper error handling.

Copilot uses AI. Check for mistakes.
Comment on lines +336 to +367
def write(self, path, data, offset, fh):
"""Write to file (handles prompts)."""
with self.lock:
if path == "/claude/prompt":
prompt = data.decode("utf-8").strip()
self.prompts["claude"] = prompt

# Generate response
response = self.client.complete(prompt, self.config)
self.responses["claude"] = response

return len(data)

elif path == "/claude/config":
try:
new_config = json.loads(data.decode("utf-8"))
self.config.update(new_config)
except json.JSONDecodeError:
pass
return len(data)

elif path.startswith("/sessions/"):
return self._write_session_file(path, data)

else:
# Store in generic files dict
if path in self.files:
self.files[path] = data
return len(data)

raise FuseOSError(errno.EACCES)

Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The offset parameter is ignored in the write method. FUSE may call write with non-zero offsets for appending or partial writes. Ignoring the offset means data could be written incorrectly or lost. For write-once files like prompt, this may be acceptable, but it should be documented or validated (e.g., raise an error if offset != 0).

Copilot uses AI. Check for mistakes.
"""

import os
import sys
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Import of 'sys' is not used.

Suggested change
import sys

Copilot uses AI. Check for mistakes.
import threading
from pathlib import Path
from datetime import datetime, timezone
from typing import Dict, Optional, Any
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Import of 'Optional' is not used.
Import of 'Any' is not used.

Suggested change
from typing import Dict, Optional, Any
from typing import Dict

Copilot uses AI. Check for mistakes.
try:
new_config = json.loads(data.decode("utf-8"))
self.config.update(new_config)
except json.JSONDecodeError:
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
try:
new_config = json.loads(data.decode("utf-8"))
session.config.update(new_config)
except json.JSONDecodeError:
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
…rations (cortexlinux#223)

Implements bounty cortexlinux#223: /dev/llm Virtual Device providing file-like interface to LLM operations.

Features:
- FUSE filesystem providing LLM access via files
- Claude API integration with mock fallback for testing
- Session management for stateful conversations
- Configuration and metrics tracking
- 20/20 tests passing

Files:
- llm_device.py: Main FUSE implementation (~574 lines)
- test_llm_device.py: Comprehensive test suite (~176 lines)
- __init__.py: Package exports

Usage:
  python -m cortex.kernel_features.llm_device.llm_device mount /mnt/llm
  echo "What is 2+2?" > /mnt/llm/claude/prompt
  cat /mnt/llm/claude/response
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 4, 2025

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.

[Kernel Feature] /dev/llm Virtual Device - FUSE-Based LLM Interface

2 participants