TOON Optimizations

This commit is contained in:
Renn F
2025-12-12 18:08:06 +01:00
parent d570334e04
commit 316325625c
17 changed files with 780 additions and 324 deletions
+14 -1
View File
@@ -36,6 +36,7 @@ dependencies = [
"anthropic", "anthropic",
"openai", # For embeddings "openai", # For embeddings
"tiktoken", # Token counting "tiktoken", # Token counting
"python-toon", # Token-efficient LLM serialization
# MCP (Model Context Protocol) # MCP (Model Context Protocol)
"mcp", "mcp",
@@ -120,11 +121,23 @@ select = [
"SIM", # flake8-simplify "SIM", # flake8-simplify
"TCH", # flake8-type-checking "TCH", # flake8-type-checking
"PTH", # flake8-use-pathlib "PTH", # flake8-use-pathlib
"ERA", # eradicate (commented code)
"PL", # Pylint "PL", # Pylint
"RUF", # Ruff-specific "RUF", # Ruff-specific
] ]
# MCP servers: Tool functions require explicit parameters for schema generation.
# PLR0913 - MCP tools need many params (no dataclass workaround)
# PLR0915 - Factory functions define all tools (unavoidable nesting)
# PLC0415 - Lazy imports for circular import avoidance
# E501 - Docstrings and guidance messages
#
# Services: Internal methods often need multiple related parameters for DB ops.
# PLR0913 - Service methods wrapping DB/external calls need multiple params
# PLC0415 - Lazy imports for circular import avoidance
[tool.ruff.lint.per-file-ignores]
"roboco/mcp/*.py" = ["PLR0913", "PLR0915", "PLC0415", "E501"]
"roboco/services/*.py" = ["PLC0415", "PLR0913"]
# ============================================================================= # =============================================================================
# MyPy Configuration # MyPy Configuration
# ============================================================================= # =============================================================================
+48
View File
@@ -21,6 +21,7 @@ from pydantic import BaseModel, Field
from roboco.api.websocket import broadcast_agent_chunk from roboco.api.websocket import broadcast_agent_chunk
from roboco.config import settings from roboco.config import settings
from roboco.llm import ToonAdapter
from roboco.models import AgentRole, AgentStatus, Team from roboco.models import AgentRole, AgentStatus, Team
logger = structlog.get_logger() logger = structlog.get_logger()
@@ -113,6 +114,7 @@ class Agent(ABC):
self._running = False self._running = False
self._task: asyncio.Task | None = None self._task: asyncio.Task | None = None
self._llm_client: AsyncAnthropic | None = None self._llm_client: AsyncAnthropic | None = None
self._toon = ToonAdapter()
self.log = logger.bind( self.log = logger.bind(
agent_id=str(config.id), agent_id=str(config.id),
@@ -157,6 +159,52 @@ class Agent(ABC):
self._llm_client = AsyncAnthropic(api_key=settings.anthropic_api_key) self._llm_client = AsyncAnthropic(api_key=settings.anthropic_api_key)
return self._llm_client return self._llm_client
# =========================================================================
# TOON SERIALIZATION (for token-efficient LLM communication)
# =========================================================================
def format_context(self, data: dict[str, Any]) -> str:
"""
Format context data for LLM using TOON.
TOON (Token-Oriented Object Notation) reduces token consumption
by 30-60% compared to JSON while maintaining semantic clarity.
Args:
data: Dictionary to encode for LLM prompt.
Returns:
TOON-formatted string.
"""
return self._toon.encode(data)
def format_context_labeled(self, label: str, data: dict[str, Any]) -> str:
"""
Format labeled context data for embedding in prompts.
Args:
label: Section label (e.g., "Task Context").
data: Dictionary to encode.
Returns:
Labeled TOON-formatted string.
"""
return self._toon.format_for_prompt(label, data)
def parse_llm_response(self, response: str) -> dict[str, Any] | list[Any]:
"""
Parse structured data from LLM response.
Attempts TOON parsing first, falls back to JSON.
Args:
response: Raw LLM response text.
Returns:
Parsed Python dict or list.
"""
return self._toon.decode(response)
# ========================================================================= # =========================================================================
# LIFECYCLE METHODS # LIFECYCLE METHODS
# ========================================================================= # =========================================================================
+23 -21
View File
@@ -153,17 +153,20 @@ class ProductOwnerAgent(Agent):
result = await self._api_call("GET", f"/tasks/{task_id}") result = await self._api_call("GET", f"/tasks/{task_id}")
acceptance_criteria = result.get("acceptance_criteria", []) acceptance_criteria = result.get("acceptance_criteria", [])
# Use LLM to check if criteria are met # Use TOON for token-efficient context encoding
prompt = f""" task_context = self.format_context_labeled(
Review this completed feature against its acceptance criteria: "Feature Review",
{
"title": result.get("title", "Unknown"),
"description": result.get("description", "No description"),
"acceptance_criteria": acceptance_criteria,
"dev_notes": result.get("dev_notes", "None"),
},
)
Task: {result.get("title", "Unknown")} prompt = f"""Review this completed feature against its acceptance criteria:
Description: {result.get("description", "No description")}
Acceptance Criteria: {task_context}
{chr(10).join(f"- {c}" for c in acceptance_criteria)}
Dev Notes: {result.get("dev_notes", "None")}
Determine if all criteria are met. Respond with: Determine if all criteria are met. Respond with:
ACCEPTED: [reason] or NEEDS_CHANGES: [what's missing] ACCEPTED: [reason] or NEEDS_CHANGES: [what's missing]
@@ -465,11 +468,15 @@ class AuditorAgent(Agent):
if not self._observations: if not self._observations:
return return
prompt = f""" # Use TOON for token-efficient context encoding
Analyze these observations for quality and efficiency issues: observations_context = self.format_context_labeled(
"Observations",
{"recent": self._observations[-50:]},
)
Observations: prompt = f"""Analyze these observations for quality and efficiency issues:
{chr(10).join(str(o) for o in self._observations[-50:])}
{observations_context}
Look for: Look for:
1. Efficiency issues - wasted effort, unclear processes 1. Efficiency issues - wasted effort, unclear processes
@@ -478,14 +485,9 @@ Look for:
4. Process violations - skipping QA, missing documentation 4. Process violations - skipping QA, missing documentation
5. Team health - frustration, conflicts 5. Team health - frustration, conflicts
For each issue found, provide: Format response as TOON tabular:
- Category [N,]{{category,severity,description,evidence,recommendation}}:
- Severity (info/warning/concern/critical) efficiency,warning,Unclear handoff process,3 tasks delayed,Document handoff steps
- Description
- Evidence
- Recommendation
Be thorough but fair.
""" """
analysis = await self.think(prompt) analysis = await self.think(prompt)
self.log.info("Analysis complete", analysis_length=len(analysis)) self.log.info("Analysis complete", analysis_length=len(analysis))
+57 -24
View File
@@ -249,13 +249,16 @@ class DeveloperAgent(Agent):
# Read task requirements # Read task requirements
requirements = await self._read_task_requirements(ctx.task_id) requirements = await self._read_task_requirements(ctx.task_id)
# Use LLM to understand and identify gaps # Format context using TOON for token efficiency
prompt = f""" task_context = self.format_context_labeled(
You are analyzing a task before beginning work. "Task Context",
{"title": ctx.title, "requirements": requirements},
)
Task: {ctx.title} # Use LLM to understand and identify gaps
Requirements: prompt = f"""You are analyzing a task before beginning work.
{requirements}
{task_context}
Analyze: Analyze:
1. What exactly needs to be done? 1. What exactly needs to be done?
@@ -293,28 +296,48 @@ If clarification needed, respond with: "QUESTION: [your question]"
""" """
self.log.info("PLAN phase", task_id=str(ctx.task_id)) self.log.info("PLAN phase", task_id=str(ctx.task_id))
# Use LLM to create plan # Format context using TOON
prompt = f""" plan_context = self.format_context_labeled(
Create an implementation plan for this task: "Task",
{
"title": ctx.title,
"understanding": ctx.journal_entries[-1]
if ctx.journal_entries
else "No context",
},
)
Task: {ctx.title} # Use LLM to create plan - request TOON tabular response
Understanding: {ctx.journal_entries[-1] if ctx.journal_entries else "No context"} prompt = f"""Create an implementation plan for this task:
Break this into ordered subtasks. For each subtask: {plan_context}
Break this into ordered subtasks. For each subtask provide:
- Clear description - Clear description
- Files to modify - Files to modify
- Estimated complexity (small/medium/large) - Estimated complexity (small/medium/large)
Format as JSON array: Format response as TOON tabular:
[ [N,]{{description,files,complexity}}:
{{"description": "...", "files": ["..."], "complexity": "small|medium|large"}}, Implement the main logic,src/main.py|src/utils.py,medium
... Add unit tests,tests/test_main.py,small
]
""" """
response = await self.think(prompt) response = await self.think(prompt)
# Parse subtasks (simplified - would use proper JSON parsing) # Parse subtasks using TOON (falls back to JSON)
ctx.subtasks = [{"description": response, "files": [], "complexity": "medium"}] try:
subtasks = self.parse_llm_response(response)
if isinstance(subtasks, list):
ctx.subtasks = subtasks
else:
ctx.subtasks = [
{"description": response, "files": [], "complexity": "medium"}
]
except ValueError:
# Fallback if parsing fails
ctx.subtasks = [
{"description": response, "files": [], "complexity": "medium"}
]
# Journal entry # Journal entry
ts = datetime.now(UTC).isoformat() ts = datetime.now(UTC).isoformat()
@@ -349,12 +372,22 @@ Format as JSON array:
subtask = ctx.subtasks[ctx.current_subtask] subtask = ctx.subtasks[ctx.current_subtask]
# Use LLM to work on subtask # Format context using TOON
prompt = f""" execute_context = self.format_context_labeled(
Execute this subtask: "Execution Context",
{
"task": ctx.title,
"subtask_number": ctx.current_subtask + 1,
"total_subtasks": len(ctx.subtasks),
"description": subtask.get("description", ""),
"files": subtask.get("files", []),
},
)
Task: {ctx.title} # Use LLM to work on subtask
Subtask {ctx.current_subtask + 1}/{len(ctx.subtasks)}: {subtask.get("description", "")} prompt = f"""Execute this subtask:
{execute_context}
Provide: Provide:
1. Code changes needed 1. Code changes needed
+27 -18
View File
@@ -321,19 +321,21 @@ Respond with structured analysis.
doc_spec = ctx.documents_needed[ctx.current_doc] doc_spec = ctx.documents_needed[ctx.current_doc]
# Use LLM to write documentation # Use TOON for token-efficient context encoding
prompt = f""" doc_context = self.format_context_labeled(
Write documentation for this task. "Documentation Task",
{
"title": ctx.title,
"doc_type": doc_spec.doc_type.value,
"target_path": doc_spec.path,
"summary": ctx.summary,
"dev_notes": ctx.dev_notes or "None",
},
)
Task: {ctx.title} prompt = f"""Write documentation for this task.
Document Type: {doc_spec.doc_type.value}
Target Path: {doc_spec.path}
Summary: {doc_context}
{ctx.summary}
Developer Notes:
{ctx.dev_notes or "None"}
Write professional, clear documentation following best practices. Write professional, clear documentation following best practices.
Include: Include:
@@ -373,14 +375,19 @@ Format appropriately for the document type.
if not doc_spec.content: if not doc_spec.content:
continue continue
prompt = f""" # Use TOON for token-efficient context encoding
Review this documentation for quality: review_context = self.format_context_labeled(
"Document Review",
{
"title": doc_spec.title,
"doc_type": doc_spec.doc_type.value,
"content": doc_spec.content,
},
)
Document: {doc_spec.title} prompt = f"""Review this documentation for quality:
Type: {doc_spec.doc_type.value}
Content: {review_context}
{doc_spec.content}
Check: Check:
1. Accuracy - Does it correctly describe the feature? 1. Accuracy - Does it correctly describe the feature?
@@ -388,7 +395,9 @@ Check:
3. Clarity - Is it easy to understand? 3. Clarity - Is it easy to understand?
4. Examples - Are examples helpful and correct? 4. Examples - Are examples helpful and correct?
If issues found, provide suggestions. Format response as TOON:
{{accuracy,completeness,clarity,examples,suggestions}}:
good,complete,clear,helpful,None
""" """
review = await self.think(prompt) review = await self.think(prompt)
ts = datetime.now(UTC).isoformat() ts = datetime.now(UTC).isoformat()
+51 -28
View File
@@ -199,12 +199,15 @@ class CellPMAgent(Agent):
new_tasks = await self._get_unassigned_tasks() new_tasks = await self._get_unassigned_tasks()
for task_id in new_tasks: for task_id in new_tasks:
# Assess complexity and priority # Use TOON for token-efficient context encoding
prompt = f""" triage_context = self.format_context_labeled(
Assess this task for prioritization: "Task Triage",
{"task_id": str(task_id), "cell": self.cell_name},
)
Task ID: {task_id} prompt = f"""Assess this task for prioritization:
Cell: {self.cell_name}
{triage_context}
Consider: Consider:
1. Complexity (low/medium/high) 1. Complexity (low/medium/high)
@@ -212,7 +215,9 @@ Consider:
3. Priority (P0-P3) 3. Priority (P0-P3)
4. Best dev fit based on skills 4. Best dev fit based on skills
Provide assessment. Format response as TOON:
{{complexity,dependencies,priority,dev_fit}}:
medium,TASK-abc123,P1,backend-dev-1
""" """
assessment = await self.think(prompt) assessment = await self.think(prompt)
self.log.info( self.log.info(
@@ -249,11 +254,15 @@ Provide assessment.
questions = await self._get_pending_questions() questions = await self._get_pending_questions()
for question in questions: for question in questions:
# Try to answer or route appropriately # Use TOON for token-efficient context encoding
prompt = f""" question_context = self.format_context_labeled(
A cell member has a question: "Cell Question",
{"question": question, "cell": self.cell_name},
)
{question} prompt = f"""A cell member needs help:
{question_context}
As the Cell PM, provide: As the Cell PM, provide:
1. Answer if you can 1. Answer if you can
@@ -610,23 +619,29 @@ class MainPMAgent(Agent):
self.log.debug("PRIORITIZE phase") self.log.debug("PRIORITIZE phase")
if self._board_directives: if self._board_directives:
directives = chr(10).join(f"- {d}" for d in self._board_directives) # Build status data for TOON encoding
status_lines = [] cell_status_data = {
for k, v in self._cell_statuses.items(): name: {"active": s.active_tasks, "blocked": s.blocked_tasks}
active = v.active_tasks for name, s in self._cell_statuses.items()
blocked = v.blocked_tasks }
status_lines.append(f"- {k}: {active} active, {blocked} blocked")
cell_status = chr(10).join(status_lines)
prompt = f"""
Translate these Board directives into cell priorities:
Directives: # Use TOON for token-efficient context encoding
{directives} priority_context = self.format_context_labeled(
"Prioritization Context",
{
"directives": self._board_directives,
"cell_status": cell_status_data,
},
)
Current Cell Status: prompt = f"""Translate these Board directives into cell priorities:
{cell_status}
Provide prioritized task list for each cell. {priority_context}
Format response as TOON tabular:
[N,]{{cell,priority,task_description}}:
backend-cell,P0,Implement critical auth fix
frontend-cell,P1,Update dashboard layout
""" """
priorities = await self.think(prompt) priorities = await self.think(prompt)
self.log.info("Priorities set", priorities=priorities[:200]) self.log.info("Priorities set", priorities=priorities[:200])
@@ -636,11 +651,19 @@ Provide prioritized task list for each cell.
self.log.debug("COORDINATE phase") self.log.debug("COORDINATE phase")
for issue in self._cross_cell_issues: for issue in self._cross_cell_issues:
prompt = f""" # Use TOON for token-efficient context encoding
Resolve this cross-cell issue: issue_context = self.format_context_labeled(
"Cross-Cell Issue",
{
"description": issue.get("description"),
"cells": issue.get("cells"),
"task_id": issue.get("task_id"),
},
)
Issue: {issue.get("description")} prompt = f"""Resolve this cross-cell issue:
Cells Involved: {issue.get("cells")}
{issue_context}
Propose a resolution that unblocks all parties. Propose a resolution that unblocks all parties.
""" """
+33 -34
View File
@@ -211,27 +211,22 @@ class QAAgent(Agent):
dev_notes = await self._read_dev_notes(ctx.task_id) dev_notes = await self._read_dev_notes(ctx.task_id)
commits = await self._get_task_commits(ctx.task_id) commits = await self._get_task_commits(ctx.task_id)
# Use LLM to understand and create test plan # Use TOON for token-efficient context encoding
prompt = f""" task_context = self.format_context_labeled(
You are a QA engineer reviewing a completed task. "QA Review Context",
{
"title": ctx.title,
"requirements": requirements,
"dev_notes": dev_notes,
"commits": commits,
},
)
Task: {ctx.title} prompt = f"""You are a QA engineer reviewing a completed task.
Requirements: {task_context}
{requirements}
Developer Notes:
{dev_notes}
Commits:
{commits}
Based on this, create test cases to verify the implementation. Based on this, create test cases to verify the implementation.
For each test case provide:
1. Name
2. What to test
3. Steps to execute
4. Expected outcome
Focus on: Focus on:
- Acceptance criteria verification - Acceptance criteria verification
@@ -239,8 +234,10 @@ Focus on:
- Integration points - Integration points
- Error handling - Error handling
Format as JSON array. Format response as TOON tabular:
""" [N,]{{name,description,steps,expected}}:
Acceptance Criteria,Verify all criteria met,Review implementation|Check each criterion,All criteria satisfied
""" # noqa: E501
_response = await self.think(prompt) # Response informs test case structure _response = await self.think(prompt) # Response informs test case structure
# Create test cases (simplified parsing) # Create test cases (simplified parsing)
@@ -289,24 +286,26 @@ Format as JSON array.
test_case = ctx.test_cases[ctx.current_test] test_case = ctx.test_cases[ctx.current_test]
# Use LLM to execute test # Use TOON for token-efficient context encoding
prompt = f""" test_context = self.format_context_labeled(
Execute this test case: "Test Case",
{
"name": test_case.name,
"description": test_case.description,
"steps": test_case.steps,
"expected": test_case.expected,
},
)
Test: {test_case.name} prompt = f"""Execute this test case:
Description: {test_case.description}
Steps: {", ".join(test_case.steps)}
Expected: {test_case.expected}
Simulate executing this test and provide: {test_context}
1. RESULT: PASS or FAIL
2. ACTUAL: What was observed
3. NOTES: Any additional findings
Format: Simulate executing this test and provide results.
RESULT: [PASS|FAIL]
ACTUAL: [observation] Format response as TOON:
NOTES: [notes] {{result,actual,notes}}:
PASS,All criteria verified successfully,No issues found
""" """
response = await self.think(prompt) response = await self.think(prompt)
+15
View File
@@ -0,0 +1,15 @@
"""
LLM Communication Layer
Provides utilities for efficient communication with Large Language Models,
including TOON serialization for token-efficient data transfer.
"""
from roboco.llm.metrics import ToonMetrics
from roboco.llm.toon_adapter import ToonAdapter, ToonConfig
__all__ = [
"ToonAdapter",
"ToonConfig",
"ToonMetrics",
]
+86
View File
@@ -0,0 +1,86 @@
"""
TOON Metrics
Tracks token savings and usage statistics for TOON vs JSON serialization.
"""
from dataclasses import dataclass, field
from datetime import UTC, datetime
@dataclass
class ToonMetrics:
"""
Metrics for tracking TOON serialization efficiency.
Tracks character counts (as proxy for tokens) for JSON vs TOON
to measure actual savings in production.
"""
json_chars: int = 0
toon_chars: int = 0
encode_count: int = 0
decode_count: int = 0
decode_fallback_count: int = 0
started_at: datetime = field(default_factory=lambda: datetime.now(UTC))
@property
def savings_percent(self) -> float:
"""Calculate percentage of characters saved using TOON."""
if self.json_chars == 0:
return 0.0
return (1 - self.toon_chars / self.json_chars) * 100
@property
def fallback_rate(self) -> float:
"""Calculate rate of fallback to JSON decoding."""
if self.decode_count == 0:
return 0.0
return (self.decode_fallback_count / self.decode_count) * 100
def record_encode(self, json_chars: int, toon_chars: int) -> None:
"""Record an encode operation with character counts."""
self.json_chars += json_chars
self.toon_chars += toon_chars
self.encode_count += 1
def record_decode(self, used_fallback: bool = False) -> None:
"""Record a decode operation."""
self.decode_count += 1
if used_fallback:
self.decode_fallback_count += 1
def to_dict(self) -> dict:
"""Convert metrics to dictionary for logging/reporting."""
return {
"json_chars": self.json_chars,
"toon_chars": self.toon_chars,
"savings_percent": round(self.savings_percent, 2),
"encode_count": self.encode_count,
"decode_count": self.decode_count,
"fallback_rate": round(self.fallback_rate, 2),
"started_at": self.started_at.isoformat(),
}
def reset(self) -> None:
"""Reset all metrics."""
self.json_chars = 0
self.toon_chars = 0
self.encode_count = 0
self.decode_count = 0
self.decode_fallback_count = 0
self.started_at = datetime.now(UTC)
# Global metrics holder
class _MetricsHolder:
"""Holder for singleton ToonMetrics instance."""
instance: ToonMetrics | None = None
def get_toon_metrics() -> ToonMetrics:
"""Get the global TOON metrics instance."""
if _MetricsHolder.instance is None:
_MetricsHolder.instance = ToonMetrics()
return _MetricsHolder.instance
+191
View File
@@ -0,0 +1,191 @@
"""
TOON Adapter
Provides serialization/deserialization between Python objects and TOON format
for token-efficient LLM communication. TOON (Token-Oriented Object Notation)
achieves 30-60% fewer tokens than JSON while maintaining semantic clarity.
Usage:
adapter = ToonAdapter()
# Encode data for LLM prompt
toon_str = adapter.encode({"name": "Alice", "age": 30})
# Decode LLM response (falls back to JSON if TOON fails)
data = adapter.decode(response_text)
# Format for embedding in prompt
prompt_section = adapter.format_for_prompt("Task Context", task_data)
"""
import json
from dataclasses import dataclass
from typing import Any
import structlog
import toon
from pydantic import BaseModel
logger = structlog.get_logger()
@dataclass
class ToonConfig:
"""Configuration for TOON encoding."""
delimiter: str = ","
indent: int = 2
include_length: bool = True
class ToonAdapter:
"""
Adapter for TOON serialization at LLM boundaries.
Converts Python dicts/Pydantic models to TOON for sending to LLMs,
and parses TOON responses back to Python objects. Falls back to
JSON parsing if TOON decode fails.
"""
def __init__(self, config: ToonConfig | None = None) -> None:
"""
Initialize the TOON adapter.
Args:
config: Optional configuration for TOON encoding.
"""
self.config = config or ToonConfig()
self.log = logger.bind(component="toon_adapter")
def encode(self, data: dict[str, Any] | list[Any] | BaseModel) -> str:
"""
Convert Python object to TOON for LLM consumption.
Args:
data: Dictionary, list, or Pydantic model to encode.
Returns:
TOON-formatted string.
"""
if isinstance(data, BaseModel):
data = data.model_dump()
return toon.encode(data, indent=self.config.indent)
def decode(self, toon_str: str) -> dict[str, Any] | list[Any]:
"""
Parse TOON response from LLM.
Falls back to JSON parsing if TOON decode fails, logging a warning.
Args:
toon_str: TOON-formatted string from LLM response.
Returns:
Parsed Python dict or list.
Raises:
ValueError: If neither TOON nor JSON parsing succeeds.
"""
# Try TOON first
toon_err_msg = ""
try:
return toon.decode(toon_str)
except Exception as toon_error:
toon_err_msg = str(toon_error)
self.log.warning(
"TOON decode failed, trying JSON fallback",
error=toon_err_msg,
)
# Fallback to JSON
try:
return json.loads(toon_str)
except json.JSONDecodeError as json_error:
self.log.error(
"Both TOON and JSON decode failed",
toon_error=toon_err_msg,
json_error=str(json_error),
)
raise ValueError(
f"Failed to decode response as TOON or JSON: {toon_str[:100]}..."
) from json_error
def format_for_prompt(self, label: str, data: dict[str, Any]) -> str:
"""
Format data with label for embedding in LLM prompt.
Args:
label: Section label (e.g., "Task Context", "Requirements").
data: Data to encode.
Returns:
Formatted string suitable for prompt inclusion.
"""
encoded = self.encode(data)
return f"{label}:\n{encoded}"
def format_tabular_request(
self,
fields: list[str],
description: str,
example_rows: list[list[str]] | None = None,
) -> str:
"""
Format a request for tabular TOON response.
Args:
fields: Column names for the table.
description: What the LLM should return.
example_rows: Optional example data rows.
Returns:
Formatted instruction for LLM to return TOON tabular data.
"""
fields_str = ",".join(fields)
header = f"[N,]{{{fields_str}}}:"
instruction = f"{description}\n\nFormat response as TOON tabular:\n{header}"
if example_rows:
instruction += "\n"
for row in example_rows:
instruction += f"{self.config.delimiter.join(row)}\n"
return instruction
def estimate_token_savings(
self,
data: dict[str, Any] | list[Any],
) -> tuple[int, int, float]:
"""
Estimate token savings of TOON vs JSON for given data.
Args:
data: Data to compare.
Returns:
Tuple of (json_chars, toon_chars, savings_percent).
"""
json_str = json.dumps(data, separators=(",", ":"))
toon_str = self.encode(data)
json_chars = len(json_str)
toon_chars = len(toon_str)
savings = (1 - toon_chars / json_chars) * 100 if json_chars > 0 else 0.0
return json_chars, toon_chars, savings
# Module-level singleton holder
class _AdapterHolder:
"""Holder for singleton ToonAdapter instance."""
instance: ToonAdapter | None = None
def get_toon_adapter() -> ToonAdapter:
"""Get the default TOON adapter singleton."""
if _AdapterHolder.instance is None:
_AdapterHolder.instance = ToonAdapter()
return _AdapterHolder.instance
+5
View File
@@ -21,6 +21,10 @@ from fastapi import status
from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import FastMCP
from roboco.config import settings from roboco.config import settings
from roboco.llm import ToonAdapter
# Global TOON adapter for encoding journal data
_toon = ToonAdapter()
# ============================================================================= # =============================================================================
# HELPER FUNCTIONS # HELPER FUNCTIONS
@@ -141,6 +145,7 @@ def create_journal_mcp_server(agent_id: str) -> FastMCP:
return { return {
"status": "created", "status": "created",
"entry": entry, "entry": entry,
"entry_toon": _toon.encode(entry), # TOON-encoded for LLM token efficiency
"guidance": "Journal entry saved. Use roboco_journal_search to find past entries.", "guidance": "Journal entry saved. Use roboco_journal_search to find past entries.",
} }
+4
View File
@@ -21,6 +21,10 @@ from mcp.server.fastmcp import FastMCP
from roboco.agents_config import CHANNEL_ACCESS from roboco.agents_config import CHANNEL_ACCESS
from roboco.config import settings from roboco.config import settings
from roboco.llm import ToonAdapter
# Global TOON adapter for encoding message data
_toon = ToonAdapter()
def _check_channel_access(agent_id: str, channel_slug: str, action: str) -> bool: def _check_channel_access(agent_id: str, channel_slug: str, action: str) -> bool:
+15 -1
View File
@@ -27,6 +27,10 @@ from fastapi import status
from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import FastMCP
from roboco.config import settings from roboco.config import settings
from roboco.llm import ToonAdapter
# Global TOON adapter for encoding task data
_toon = ToonAdapter()
# ============================================================================= # =============================================================================
# VALID STATE TRANSITIONS # VALID STATE TRANSITIONS
@@ -63,15 +67,25 @@ def _format_task_response(
guidance: str, guidance: str,
project: dict[str, Any] | None = None, project: dict[str, Any] | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Format a standardized task response with guidance.""" """
Format a standardized task response with guidance.
Includes both JSON task data and TOON-encoded version for
token-efficient LLM consumption.
"""
# Encode task data as TOON for token efficiency when LLM processes response
task_toon = _toon.encode(task)
response = { response = {
"status": task.get("status"), "status": task.get("status"),
"task": task, "task": task,
"task_toon": task_toon, # TOON-encoded for LLM token efficiency
"next_step": next_step, "next_step": next_step,
"guidance": guidance, "guidance": guidance,
} }
if project: if project:
response["project"] = project response["project"] = project
response["project_toon"] = _toon.encode(project)
return response return response
+22 -16
View File
@@ -5,6 +5,7 @@ Documenter handoffs contain all the information needed for
a Documenter to create production documentation from developer work. a Documenter to create production documentation from developer work.
""" """
from dataclasses import dataclass, field
from datetime import UTC, datetime from datetime import UTC, datetime
from uuid import UUID, uuid4 from uuid import UUID, uuid4
@@ -225,24 +226,29 @@ class DocumenterHandoff(TimestampMixin):
# ============================================================================= # =============================================================================
def create_handoff( @dataclass
task_id: UUID, class HandoffParams:
summary: str, """Parameters for creating a handoff document."""
commits: list[dict[str, str]],
dev_notes_location: str, task_id: UUID
new_functionality: list[str] | None = None, summary: str
modified_behavior: list[str] | None = None, commits: list[dict[str, str]]
breaking_changes: list[str] | None = None, dev_notes_location: str
) -> DocumenterHandoff: new_functionality: list[str] = field(default_factory=list)
modified_behavior: list[str] = field(default_factory=list)
breaking_changes: list[str] = field(default_factory=list)
def create_handoff(params: HandoffParams) -> DocumenterHandoff:
"""Create a basic handoff document.""" """Create a basic handoff document."""
handoff = DocumenterHandoff( handoff = DocumenterHandoff(
task_id=task_id, task_id=params.task_id,
summary=summary, summary=params.summary,
commits=commits, commits=params.commits,
dev_notes_location=dev_notes_location, dev_notes_location=params.dev_notes_location,
new_functionality=new_functionality or [], new_functionality=params.new_functionality,
modified_behavior=modified_behavior or [], modified_behavior=params.modified_behavior,
breaking_changes=breaking_changes or [], breaking_changes=params.breaking_changes,
) )
# Always add changelog as required # Always add changelog as required
+117 -91
View File
@@ -5,6 +5,7 @@ Personal agent journals for reflection, growth tracking, and debugging.
Each agent maintains their own journal with entries tied to tasks and sessions. Each agent maintains their own journal with entries tied to tasks and sessions.
""" """
from dataclasses import dataclass, field
from datetime import UTC, datetime from datetime import UTC, datetime
from uuid import UUID, uuid4 from uuid import UUID, uuid4
@@ -114,170 +115,195 @@ class Journal(TimestampMixin):
# ============================================================================= # =============================================================================
def create_task_reflection( @dataclass
journal_id: UUID, class TaskReflectionParams:
task_id: UUID, """Parameters for creating a task reflection entry."""
title: str,
what_done: str, journal_id: UUID
what_learned: str, task_id: UUID
what_struggled: str, title: str
next_steps: list[str], what_done: str
tags: list[str] | None = None, what_learned: str
) -> JournalEntry: what_struggled: str
next_steps: list[str]
tags: list[str] = field(default_factory=list)
@dataclass
class DecisionLogParams:
"""Parameters for creating a decision log entry."""
journal_id: UUID
title: str
context: str
options: list[dict[str, str]]
chosen: str
rationale: str
consequences: list[str]
task_id: UUID | None = None
tags: list[str] = field(default_factory=list)
@dataclass
class LearningEntryParams:
"""Parameters for creating a learning entry."""
journal_id: UUID
title: str
what_learned: str
how_applied: str | None = None
source: str | None = None
task_id: UUID | None = None
tags: list[str] = field(default_factory=list)
@dataclass
class StruggleEntryParams:
"""Parameters for creating a struggle entry."""
journal_id: UUID
title: str
what_struggled: str
attempted_solutions: list[str]
resolution: str | None = None
help_needed: str | None = None
task_id: UUID | None = None
tags: list[str] = field(default_factory=list)
@dataclass
class GeneralEntryParams:
"""Parameters for creating a general journal entry."""
journal_id: UUID
title: str
content: str
task_id: UUID | None = None
session_id: UUID | None = None
tags: list[str] = field(default_factory=list)
is_private: bool = False
def create_task_reflection(params: TaskReflectionParams) -> JournalEntry:
"""Create a task reflection entry.""" """Create a task reflection entry."""
content = f"""## What I Did content = f"""## What I Did
{what_done} {params.what_done}
## What I Learned ## What I Learned
{what_learned} {params.what_learned}
## What I Struggled With ## What I Struggled With
{what_struggled} {params.what_struggled}
## Next Steps ## Next Steps
{chr(10).join(f"- [ ] {step}" for step in next_steps)} {chr(10).join(f"- [ ] {step}" for step in params.next_steps)}
""" """
return JournalEntry( return JournalEntry(
journal_id=journal_id, journal_id=params.journal_id,
type=JournalEntryType.TASK_REFLECTION, type=JournalEntryType.TASK_REFLECTION,
title=title, title=params.title,
content=content, content=content,
task_id=task_id, task_id=params.task_id,
tags=tags or [], tags=params.tags,
) )
def create_decision_log( def create_decision_log(params: DecisionLogParams) -> JournalEntry:
journal_id: UUID,
title: str,
context: str,
options: list[dict[str, str]],
chosen: str,
rationale: str,
consequences: list[str],
task_id: UUID | None = None,
tags: list[str] | None = None,
) -> JournalEntry:
"""Create a decision log entry.""" """Create a decision log entry."""
options_text = "" options_text = ""
for i, opt in enumerate(options, 1): for i, opt in enumerate(params.options, 1):
options_text += f"\n**Option {i}: {opt.get('name', f'Option {i}')}**\n" options_text += f"\n**Option {i}: {opt.get('name', f'Option {i}')}**\n"
options_text += f"- Pros: {opt.get('pros', 'N/A')}\n" options_text += f"- Pros: {opt.get('pros', 'N/A')}\n"
options_text += f"- Cons: {opt.get('cons', 'N/A')}\n" options_text += f"- Cons: {opt.get('cons', 'N/A')}\n"
content = f"""## Context content = f"""## Context
{context} {params.context}
## Options Considered ## Options Considered
{options_text} {options_text}
## Decision ## Decision
Chose **{chosen}** because {rationale} Chose **{params.chosen}** because {params.rationale}
## Consequences ## Consequences
{chr(10).join(f"- {c}" for c in consequences)} {chr(10).join(f"- {c}" for c in params.consequences)}
""" """
return JournalEntry( return JournalEntry(
journal_id=journal_id, journal_id=params.journal_id,
type=JournalEntryType.DECISION_LOG, type=JournalEntryType.DECISION_LOG,
title=title, title=params.title,
content=content, content=content,
task_id=task_id, task_id=params.task_id,
tags=tags or [], tags=params.tags,
) )
def create_learning_entry( def create_learning_entry(params: LearningEntryParams) -> JournalEntry:
journal_id: UUID,
title: str,
what_learned: str,
how_applied: str | None = None,
source: str | None = None,
task_id: UUID | None = None,
tags: list[str] | None = None,
) -> JournalEntry:
"""Create a learning entry.""" """Create a learning entry."""
content = f"""## What I Learned content = f"""## What I Learned
{what_learned} {params.what_learned}
""" """
if how_applied: if params.how_applied:
content += f""" content += f"""
## How I Applied It ## How I Applied It
{how_applied} {params.how_applied}
""" """
if source: if params.source:
content += f""" content += f"""
## Source ## Source
{source} {params.source}
""" """
return JournalEntry( return JournalEntry(
journal_id=journal_id, journal_id=params.journal_id,
type=JournalEntryType.LEARNING, type=JournalEntryType.LEARNING,
title=title, title=params.title,
content=content, content=content,
task_id=task_id, task_id=params.task_id,
tags=tags or [], tags=params.tags,
sentiment="positive", sentiment="positive",
) )
def create_struggle_entry( def create_struggle_entry(params: StruggleEntryParams) -> JournalEntry:
journal_id: UUID,
title: str,
what_struggled: str,
attempted_solutions: list[str],
resolution: str | None = None,
help_needed: str | None = None,
task_id: UUID | None = None,
tags: list[str] | None = None,
) -> JournalEntry:
"""Create a struggle/difficulty entry.""" """Create a struggle/difficulty entry."""
content = f"""## What I Struggled With content = f"""## What I Struggled With
{what_struggled} {params.what_struggled}
## What I Tried ## What I Tried
{chr(10).join(f"- {s}" for s in attempted_solutions)} {chr(10).join(f"- {s}" for s in params.attempted_solutions)}
""" """
if resolution: if params.resolution:
content += f""" content += f"""
## Resolution ## Resolution
{resolution} {params.resolution}
""" """
if help_needed: if params.help_needed:
content += f""" content += f"""
## Help Needed ## Help Needed
{help_needed} {params.help_needed}
""" """
return JournalEntry( return JournalEntry(
journal_id=journal_id, journal_id=params.journal_id,
type=JournalEntryType.STRUGGLE, type=JournalEntryType.STRUGGLE,
title=title, title=params.title,
content=content, content=content,
task_id=task_id, task_id=params.task_id,
tags=tags or [], tags=params.tags,
sentiment="frustrated", sentiment="frustrated",
) )
def create_general_entry( def create_general_entry(params: GeneralEntryParams) -> JournalEntry:
journal_id: UUID,
title: str,
content: str,
task_id: UUID | None = None,
session_id: UUID | None = None,
tags: list[str] | None = None,
is_private: bool = False,
) -> JournalEntry:
"""Create a general journal entry.""" """Create a general journal entry."""
return JournalEntry( return JournalEntry(
journal_id=journal_id, journal_id=params.journal_id,
type=JournalEntryType.GENERAL, type=JournalEntryType.GENERAL,
title=title, title=params.title,
content=content, content=params.content,
task_id=task_id, task_id=params.task_id,
session_id=session_id, session_id=params.session_id,
tags=tags or [], tags=params.tags,
is_private=is_private, is_private=params.is_private,
) )
+54 -83
View File
@@ -29,6 +29,19 @@ logger = structlog.get_logger()
# Maximum length for raw excerpt storage # Maximum length for raw excerpt storage
MAX_EXCERPT_LENGTH = 200 MAX_EXCERPT_LENGTH = 200
@dataclass
class ExtractionContext:
"""Context for message extraction."""
content: str
agent_id: UUID
channel_id: UUID
session_id: UUID
group_id: UUID
task_id: UUID | None = None
# ============================================================================= # =============================================================================
# EXTRACTION PATTERNS # EXTRACTION PATTERNS
# ============================================================================= # =============================================================================
@@ -202,40 +215,27 @@ class ExtractionService:
# Mention pattern # Mention pattern
self._mention_pattern = re.compile(r"@(\w+)") self._mention_pattern = re.compile(r"@(\w+)")
async def extract( async def extract(self, ctx: ExtractionContext) -> ExtractionResult:
self,
content: str,
agent_id: UUID,
channel_id: UUID,
session_id: UUID,
group_id: UUID,
task_id: UUID | None = None,
) -> ExtractionResult:
""" """
Extract messages from raw content. Extract messages from raw content.
Args: Args:
content: Raw LLM output text ctx: Extraction context with content and metadata
agent_id: Agent who produced the content
channel_id: Target channel
session_id: Current session
group_id: Group within channel
task_id: Optional related task
Returns: Returns:
ExtractionResult with extracted messages ExtractionResult with extracted messages
""" """
if len(content) < self.config.min_content_length: if len(ctx.content) < self.config.min_content_length:
return ExtractionResult( return ExtractionResult(
messages=[], messages=[],
raw_content=content, raw_content=ctx.content,
agent_id=agent_id, agent_id=ctx.agent_id,
channel_id=channel_id, channel_id=ctx.channel_id,
session_id=session_id, session_id=ctx.session_id,
) )
# Segment the content # Segment the content
segments = self._segment_content(content) segments = self._segment_content(ctx.content)
messages: list[ExtractedMessage] = [] messages: list[ExtractedMessage] = []
pattern_matches: dict[str, list[str]] = {} pattern_matches: dict[str, list[str]] = {}
@@ -264,15 +264,15 @@ class ExtractionService:
# Create message # Create message
message = ExtractedMessage( message = ExtractedMessage(
id=uuid4(), id=uuid4(),
agent_id=agent_id, agent_id=ctx.agent_id,
channel_id=channel_id, channel_id=ctx.channel_id,
group_id=group_id, group_id=ctx.group_id,
session_id=session_id, session_id=ctx.session_id,
type=msg_type, type=msg_type,
content=segment.strip(), content=segment.strip(),
content_length=len(segment.strip()), content_length=len(segment.strip()),
mentions=mentions, mentions=mentions,
task_id=task_id, task_id=ctx.task_id,
confidence=confidence, confidence=confidence,
raw_excerpt=segment[:MAX_EXCERPT_LENGTH] raw_excerpt=segment[:MAX_EXCERPT_LENGTH]
if len(segment) > MAX_EXCERPT_LENGTH if len(segment) > MAX_EXCERPT_LENGTH
@@ -284,17 +284,17 @@ class ExtractionService:
result = ExtractionResult( result = ExtractionResult(
messages=messages, messages=messages,
raw_content=content, raw_content=ctx.content,
agent_id=agent_id, agent_id=ctx.agent_id,
channel_id=channel_id, channel_id=ctx.channel_id,
session_id=session_id, session_id=ctx.session_id,
pattern_matches=pattern_matches, pattern_matches=pattern_matches,
confidence_scores=confidence_scores, confidence_scores=confidence_scores,
) )
self.log.info( self.log.info(
"Extraction complete", "Extraction complete",
agent_id=str(agent_id), agent_id=str(ctx.agent_id),
message_count=result.message_count, message_count=result.message_count,
types=result.types_extracted, types=result.types_extracted,
) )
@@ -365,46 +365,41 @@ class ExtractionService:
return best_type, confidence, matches return best_type, confidence, matches
async def extract_with_llm( async def extract_with_llm(self, ctx: ExtractionContext) -> ExtractionResult:
self,
content: str,
agent_id: UUID,
channel_id: UUID,
session_id: UUID,
group_id: UUID,
task_id: UUID | None = None,
) -> ExtractionResult:
""" """
Extract messages using LLM classification. Extract messages using LLM classification.
This is more accurate but slower and more expensive. This is more accurate but slower and more expensive.
Falls back to pattern matching if LLM unavailable. Falls back to pattern matching if LLM unavailable.
Uses TOON format for token-efficient communication.
""" """
from anthropic import AsyncAnthropic from anthropic import AsyncAnthropic
from roboco.config import settings from roboco.config import settings
from roboco.llm import ToonAdapter
toon = ToonAdapter()
try: try:
client = AsyncAnthropic(api_key=settings.anthropic_api_key) client = AsyncAnthropic(api_key=settings.anthropic_api_key)
# Build prompt for LLM classification # Build prompt for LLM classification using TOON
prompt = f"""Analyze this agent output and classify each distinct segment. prompt = f"""Analyze this agent output and classify each distinct segment.
Agent output: Agent output:
{content} {ctx.content}
For each segment, identify: For each segment, identify:
- type: one of [reasoning, dialogue, decision, action, blocker, technical] - type: one of [reasoning, dialogue, decision, action, blocker, technical]
- content: the segment text - content: the segment text
- confidence: 0.0 to 1.0 - confidence: 0.0 to 1.0
Return as JSON array of objects. Example: Return as TOON tabular format:
[ [N,]{{type,content,confidence}}:
{{"type": "reasoning", "content": "Analyzing the problem...", "confidence": 0.9}}, reasoning,Analyzing the problem...,0.9
{{"type": "action", "content": "Creating file utils.py", "confidence": 0.95}} action,Creating file utils.py,0.95
]
Output only valid JSON, no other text.""" Output only valid TOON, no other text."""
response = await client.messages.create( response = await client.messages.create(
model="claude-3-haiku-20240307", # Fast, cheap for classification model="claude-3-haiku-20240307", # Fast, cheap for classification
@@ -412,11 +407,9 @@ Output only valid JSON, no other text."""
messages=[{"role": "user", "content": prompt}], messages=[{"role": "user", "content": prompt}],
) )
# Parse response # Parse response using TOON (falls back to JSON)
import json
response_text = response.content[0].text response_text = response.content[0].text
segments = json.loads(response_text) segments = toon.decode(response_text)
messages: list[ExtractedMessage] = [] messages: list[ExtractedMessage] = []
for segment in segments: for segment in segments:
@@ -430,11 +423,11 @@ Output only valid JSON, no other text."""
id=uuid4(), id=uuid4(),
content=msg_content, content=msg_content,
message_type=msg_type, message_type=msg_type,
agent_id=agent_id, agent_id=ctx.agent_id,
channel_id=channel_id, channel_id=ctx.channel_id,
session_id=session_id, session_id=ctx.session_id,
group_id=group_id, group_id=ctx.group_id,
task_id=task_id, task_id=ctx.task_id,
confidence=confidence, confidence=confidence,
metadata={"extraction_method": "llm"}, metadata={"extraction_method": "llm"},
) )
@@ -442,7 +435,7 @@ Output only valid JSON, no other text."""
return ExtractionResult( return ExtractionResult(
messages=messages, messages=messages,
raw_content=content, raw_content=ctx.content,
extraction_time=0.0, # Could measure actual time extraction_time=0.0, # Could measure actual time
confidence=sum(m.confidence for m in messages) / max(1, len(messages)), confidence=sum(m.confidence for m in messages) / max(1, len(messages)),
) )
@@ -450,14 +443,7 @@ Output only valid JSON, no other text."""
except Exception as e: except Exception as e:
# Fall back to pattern matching # Fall back to pattern matching
self.log.warning("LLM extraction failed, using patterns", error=str(e)) self.log.warning("LLM extraction failed, using patterns", error=str(e))
return await self.extract( return await self.extract(ctx)
content=content,
agent_id=agent_id,
channel_id=channel_id,
session_id=session_id,
group_id=group_id,
task_id=task_id,
)
# ============================================================================= # =============================================================================
@@ -496,26 +482,11 @@ class ExtractionPipeline:
"""Register a callback for extracted messages.""" """Register a callback for extracted messages."""
self._message_callbacks.append(callback) self._message_callbacks.append(callback)
async def process_buffer( async def process_buffer(self, ctx: ExtractionContext) -> ExtractionResult:
self,
content: str,
agent_id: UUID,
channel_id: UUID,
session_id: UUID,
group_id: UUID,
task_id: UUID | None = None,
) -> ExtractionResult:
""" """
Process a buffer and invoke callbacks for each message. Process a buffer and invoke callbacks for each message.
""" """
result = await self.extraction.extract( result = await self.extraction.extract(ctx)
content=content,
agent_id=agent_id,
channel_id=channel_id,
session_id=session_id,
group_id=group_id,
task_id=task_id,
)
# Invoke callbacks for each message # Invoke callbacks for each message
for message in result.messages: for message in result.messages:
Generated
+18 -7
View File
@@ -1796,7 +1796,7 @@ name = "nvidia-cudnn-cu12"
version = "9.10.2.21" version = "9.10.2.21"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" } source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "nvidia-cublas-cu12" }, { name = "nvidia-cublas-cu12", marker = "sys_platform != 'win32'" },
] ]
wheels = [ wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
@@ -1807,7 +1807,7 @@ name = "nvidia-cufft-cu12"
version = "11.3.3.83" version = "11.3.3.83"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" } source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "nvidia-nvjitlink-cu12" }, { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" },
] ]
wheels = [ wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
@@ -1834,9 +1834,9 @@ name = "nvidia-cusolver-cu12"
version = "11.7.3.90" version = "11.7.3.90"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" } source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "nvidia-cublas-cu12" }, { name = "nvidia-cublas-cu12", marker = "sys_platform != 'win32'" },
{ name = "nvidia-cusparse-cu12" }, { name = "nvidia-cusparse-cu12", marker = "sys_platform != 'win32'" },
{ name = "nvidia-nvjitlink-cu12" }, { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" },
] ]
wheels = [ wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
@@ -1847,7 +1847,7 @@ name = "nvidia-cusparse-cu12"
version = "12.5.8.93" version = "12.5.8.93"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" } source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "nvidia-nvjitlink-cu12" }, { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'win32'" },
] ]
wheels = [ wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
@@ -2062,7 +2062,7 @@ name = "pexpect"
version = "4.9.0" version = "4.9.0"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" } source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "ptyprocess" }, { name = "ptyprocess", marker = "sys_platform != 'win32'" },
] ]
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
wheels = [ wheels = [
@@ -2577,6 +2577,15 @@ wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" },
] ]
[[package]]
name = "python-toon"
version = "0.1.3"
source = { registry = "https://pkgs.safetycli.com/repository/renzof/pypi/simple/" }
sdist = { url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/4e/92/640c83ca46d5fe9c49895449a8932f55252537dd13dd22186cbac3a1ce59/python_toon-0.1.3.tar.gz", hash = "sha256:ca348b214c4f1cdad3579fd83dd60032d9eb87eb349c2d430ad9eb6371f174bf", size = 31280, upload-time = "2025-11-04T09:12:20.949Z" }
wheels = [
{ url = "https://pkgs.safetycli.com/package/renzof/pypi/packages/26/a4/2f2def0378b44f913d2d6cb3bc5b1a15267b363937ab1cb9afb07ce2313c/python_toon-0.1.3-py3-none-any.whl", hash = "sha256:a27b0ee4a729e730d1037d0a63eb8b344b3e5a26e3dc9a173067b6c31a868ee6", size = 21797, upload-time = "2025-11-04T09:12:19.443Z" },
]
[[package]] [[package]]
name = "pytz" name = "pytz"
version = "2025.2" version = "2025.2"
@@ -2781,6 +2790,7 @@ dependencies = [
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "python-jose", extra = ["cryptography"] }, { name = "python-jose", extra = ["cryptography"] },
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "python-toon" },
{ name = "redis" }, { name = "redis" },
{ name = "sqlalchemy", extra = ["asyncio"] }, { name = "sqlalchemy", extra = ["asyncio"] },
{ name = "structlog" }, { name = "structlog" },
@@ -2842,6 +2852,7 @@ requires-dist = [
{ name = "pytest-xdist", marker = "extra == 'dev'" }, { name = "pytest-xdist", marker = "extra == 'dev'" },
{ name = "python-jose", extras = ["cryptography"] }, { name = "python-jose", extras = ["cryptography"] },
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "python-toon" },
{ name = "redis" }, { name = "redis" },
{ name = "rich", marker = "extra == 'dev'" }, { name = "rich", marker = "extra == 'dev'" },
{ name = "ruff", marker = "extra == 'dev'" }, { name = "ruff", marker = "extra == 'dev'" },