@robbiesrobotics/alice-agents 1.5.7 → 1.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/bin/alice-cloud.cjs +71 -55
- package/package.json +1 -1
- package/templates/skills/acculynx/SKILL.md +183 -0
- package/templates/skills/acculynx/references/analysis_template.py +116 -0
- package/templates/skills/acculynx/references/dashboard_page.tsx +641 -0
- package/templates/skills/claude-code/SKILL.md +2 -2
- package/templates/skills/coding-agent/SKILL.md +68 -0
- package/templates/skills/crawl4ai/SKILL.md +119 -0
- package/templates/skills/crawl4ai/scripts/crwl +3 -0
- package/templates/workspaces/accuscope/AGENTS.md +38 -0
- package/templates/workspaces/accuscope/FEEDBACK.md +27 -0
- package/templates/workspaces/accuscope/HEARTBEAT.md +26 -0
- package/templates/workspaces/accuscope/IDENTITY.md +48 -0
- package/templates/workspaces/accuscope/LEARNINGS.md +46 -0
- package/templates/workspaces/accuscope/MEMORY.md +47 -0
- package/templates/workspaces/accuscope/PLAYBOOK.md +65 -0
- package/templates/workspaces/accuscope/SOUL.md +40 -0
- package/templates/workspaces/accuscope/TOOLS.md +63 -0
- package/templates/workspaces/accuscope/USER.md +39 -0
- package/templates/workspaces/aiden/AGENTS.md +52 -0
- package/templates/workspaces/aiden/FEEDBACK.md +12 -0
- package/templates/workspaces/aiden/HEARTBEAT.md +9 -0
- package/templates/workspaces/aiden/IDENTITY.md +6 -0
- package/templates/workspaces/aiden/LEARNINGS.md +6 -0
- package/templates/workspaces/aiden/MEMORY.md +22 -0
- package/templates/workspaces/aiden/PLAYBOOK.md +16 -0
- package/templates/workspaces/aiden/SOUL.md +1 -1
- package/templates/workspaces/aiden/USER.md +17 -0
- package/templates/workspaces/alex/AGENTS.md +52 -0
- package/templates/workspaces/alex/FEEDBACK.md +11 -0
- package/templates/workspaces/alex/HEARTBEAT.md +9 -0
- package/templates/workspaces/alex/IDENTITY.md +6 -0
- package/templates/workspaces/alex/LEARNINGS.md +5 -0
- package/templates/workspaces/alex/MEMORY.md +22 -0
- package/templates/workspaces/alex/PLAYBOOK.md +16 -0
- package/templates/workspaces/alex/SOUL.md +1 -1
- package/templates/workspaces/alex/USER.md +13 -0
- package/templates/workspaces/aria/AGENTS.md +18 -0
- package/templates/workspaces/aria/FEEDBACK.md +12 -0
- package/templates/workspaces/aria/HEARTBEAT.md +32 -0
- package/templates/workspaces/aria/IDENTITY.md +12 -0
- package/templates/workspaces/aria/LEARNINGS.md +31 -0
- package/templates/workspaces/aria/MEMORY.md +29 -0
- package/templates/workspaces/aria/PLAYBOOK.md +71 -0
- package/templates/workspaces/aria/SOUL.md +57 -0
- package/templates/workspaces/aria/TOOLS.md +47 -0
- package/templates/workspaces/aria/USER.md +18 -0
- package/templates/workspaces/audrey/AGENTS.md +59 -0
- package/templates/workspaces/audrey/FEEDBACK.md +11 -0
- package/templates/workspaces/audrey/HEARTBEAT.md +9 -0
- package/templates/workspaces/audrey/IDENTITY.md +6 -0
- package/templates/workspaces/audrey/LEARNINGS.md +5 -0
- package/templates/workspaces/audrey/MEMORY.md +22 -0
- package/templates/workspaces/audrey/PLAYBOOK.md +16 -0
- package/templates/workspaces/audrey/SOUL.md +1 -1
- package/templates/workspaces/audrey/TOOLS.md +15 -0
- package/templates/workspaces/audrey/USER.md +13 -0
- package/templates/workspaces/avery/AGENTS.md +52 -0
- package/templates/workspaces/avery/FEEDBACK.md +12 -0
- package/templates/workspaces/avery/HEARTBEAT.md +5 -0
- package/templates/workspaces/avery/IDENTITY.md +6 -0
- package/templates/workspaces/avery/LEARNINGS.md +6 -0
- package/templates/workspaces/avery/MEMORY.md +22 -0
- package/templates/workspaces/avery/PLAYBOOK.md +16 -0
- package/templates/workspaces/avery/SOUL.md +1 -1
- package/templates/workspaces/avery/USER.md +17 -0
- package/templates/workspaces/avery/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/avery/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/caleb/AGENTS.md +52 -0
- package/templates/workspaces/caleb/FEEDBACK.md +11 -0
- package/templates/workspaces/caleb/HEARTBEAT.md +9 -0
- package/templates/workspaces/caleb/IDENTITY.md +6 -0
- package/templates/workspaces/caleb/LEARNINGS.md +5 -0
- package/templates/workspaces/caleb/MEMORY.md +22 -0
- package/templates/workspaces/caleb/PLAYBOOK.md +16 -0
- package/templates/workspaces/caleb/SOUL.md +1 -1
- package/templates/workspaces/caleb/TOOLS.md +30 -0
- package/templates/workspaces/caleb/USER.md +13 -0
- package/templates/workspaces/clara/AGENTS.md +59 -0
- package/templates/workspaces/clara/FEEDBACK.md +12 -0
- package/templates/workspaces/clara/HEARTBEAT.md +5 -0
- package/templates/workspaces/clara/IDENTITY.md +6 -0
- package/templates/workspaces/clara/LEARNINGS.md +6 -0
- package/templates/workspaces/clara/MEMORY.md +22 -0
- package/templates/workspaces/clara/PLAYBOOK.md +16 -0
- package/templates/workspaces/clara/SOUL.md +1 -1
- package/templates/workspaces/clara/TOOLS.md +15 -0
- package/templates/workspaces/clara/USER.md +17 -0
- package/templates/workspaces/daphne/AGENTS.md +59 -0
- package/templates/workspaces/daphne/FEEDBACK.md +18 -0
- package/templates/workspaces/daphne/HEARTBEAT.md +5 -0
- package/templates/workspaces/daphne/IDENTITY.md +6 -0
- package/templates/workspaces/daphne/LEARNINGS.md +6 -0
- package/templates/workspaces/daphne/MEMORY.md +22 -0
- package/templates/workspaces/daphne/PLAYBOOK.md +48 -0
- package/templates/workspaces/daphne/SOUL.md +1 -1
- package/templates/workspaces/daphne/TOOLS.md +15 -0
- package/templates/workspaces/daphne/USER.md +17 -0
- package/templates/workspaces/darius/AGENTS.md +52 -0
- package/templates/workspaces/darius/FEEDBACK.md +12 -0
- package/templates/workspaces/darius/HEARTBEAT.md +5 -0
- package/templates/workspaces/darius/IDENTITY.md +6 -0
- package/templates/workspaces/darius/LEARNINGS.md +6 -0
- package/templates/workspaces/darius/MEMORY.md +22 -0
- package/templates/workspaces/darius/PLAYBOOK.md +16 -0
- package/templates/workspaces/darius/SOUL.md +1 -1
- package/templates/workspaces/darius/USER.md +17 -0
- package/templates/workspaces/darius/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/darius/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/devon/AGENTS.md +52 -0
- package/templates/workspaces/devon/FEEDBACK.md +11 -0
- package/templates/workspaces/devon/HEARTBEAT.md +5 -0
- package/templates/workspaces/devon/IDENTITY.md +6 -0
- package/templates/workspaces/devon/LEARNINGS.md +11 -0
- package/templates/workspaces/devon/MEMORY.md +22 -0
- package/templates/workspaces/devon/PLAYBOOK.md +16 -0
- package/templates/workspaces/devon/SOUL.md +1 -1
- package/templates/workspaces/devon/USER.md +13 -0
- package/templates/workspaces/devon/check_github.py +12 -0
- package/templates/workspaces/devon/check_mc_env.py +30 -0
- package/templates/workspaces/devon/check_sb.py +34 -0
- package/templates/workspaces/devon/check_vercel.py +12 -0
- package/templates/workspaces/devon/get_mc_files.py +17 -0
- package/templates/workspaces/devon/write_heartbeat.py +67 -0
- package/templates/workspaces/dylan/.env.example +33 -0
- package/templates/workspaces/dylan/00007_verify_licenses_table.sql +100 -0
- package/templates/workspaces/dylan/AGENTS.md +52 -0
- package/templates/workspaces/dylan/FEEDBACK.md +28 -0
- package/templates/workspaces/dylan/HEARTBEAT.md +5 -0
- package/templates/workspaces/dylan/IDENTITY.md +6 -0
- package/templates/workspaces/dylan/LEARNINGS.md +70 -0
- package/templates/workspaces/dylan/MEMORY.md +22 -0
- package/templates/workspaces/dylan/PLAYBOOK.md +16 -0
- package/templates/workspaces/dylan/SOUL.md +1 -1
- package/templates/workspaces/dylan/STRIPE_PIPELINE.md +185 -0
- package/templates/workspaces/dylan/USER.md +17 -0
- package/templates/workspaces/dylan/n8n-stripe-welcome-workflow.json +123 -0
- package/templates/workspaces/dylan/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/dylan/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/dylan/stripe-webhook-handler.py +433 -0
- package/templates/workspaces/dylan/test_mock_webhook.py +103 -0
- package/templates/workspaces/elena/AGENTS.md +59 -0
- package/templates/workspaces/elena/FEEDBACK.md +11 -0
- package/templates/workspaces/elena/HEARTBEAT.md +9 -0
- package/templates/workspaces/elena/IDENTITY.md +6 -0
- package/templates/workspaces/elena/LEARNINGS.md +5 -0
- package/templates/workspaces/elena/MEMORY.md +22 -0
- package/templates/workspaces/elena/PLAYBOOK.md +16 -0
- package/templates/workspaces/elena/SOUL.md +1 -1
- package/templates/workspaces/elena/TOOLS.md +15 -0
- package/templates/workspaces/elena/USER.md +13 -0
- package/templates/workspaces/eva/AGENTS.md +59 -0
- package/templates/workspaces/eva/FEEDBACK.md +11 -0
- package/templates/workspaces/eva/HEARTBEAT.md +9 -0
- package/templates/workspaces/eva/IDENTITY.md +6 -0
- package/templates/workspaces/eva/LEARNINGS.md +5 -0
- package/templates/workspaces/eva/MEMORY.md +22 -0
- package/templates/workspaces/eva/PLAYBOOK.md +16 -0
- package/templates/workspaces/eva/SOUL.md +1 -1
- package/templates/workspaces/eva/TOOLS.md +15 -0
- package/templates/workspaces/eva/USER.md +13 -0
- package/templates/workspaces/felix/AGENTS.md +52 -0
- package/templates/workspaces/felix/FEEDBACK.md +11 -0
- package/templates/workspaces/felix/HEARTBEAT.md +5 -0
- package/templates/workspaces/felix/IDENTITY.md +6 -0
- package/templates/workspaces/felix/LEARNINGS.md +17 -0
- package/templates/workspaces/felix/MEMORY.md +22 -0
- package/templates/workspaces/felix/PLAYBOOK.md +16 -0
- package/templates/workspaces/felix/SOUL.md +1 -1
- package/templates/workspaces/felix/USER.md +13 -0
- package/templates/workspaces/felix/fidelia-psychology.html +1594 -0
- package/templates/workspaces/felix/task.txt +164 -0
- package/templates/workspaces/hannah/AGENTS.md +59 -0
- package/templates/workspaces/hannah/FEEDBACK.md +12 -0
- package/templates/workspaces/hannah/HEARTBEAT.md +5 -0
- package/templates/workspaces/hannah/IDENTITY.md +6 -0
- package/templates/workspaces/hannah/LEARNINGS.md +6 -0
- package/templates/workspaces/hannah/MEMORY.md +22 -0
- package/templates/workspaces/hannah/PLAYBOOK.md +16 -0
- package/templates/workspaces/hannah/SOUL.md +1 -1
- package/templates/workspaces/hannah/TOOLS.md +15 -0
- package/templates/workspaces/hannah/USER.md +17 -0
- package/templates/workspaces/isaac/AGENTS.md +52 -0
- package/templates/workspaces/isaac/FEEDBACK.md +12 -0
- package/templates/workspaces/isaac/HEARTBEAT.md +9 -0
- package/templates/workspaces/isaac/IDENTITY.md +6 -0
- package/templates/workspaces/isaac/LEARNINGS.md +6 -0
- package/templates/workspaces/isaac/MEMORY.md +22 -0
- package/templates/workspaces/isaac/PLAYBOOK.md +16 -0
- package/templates/workspaces/isaac/SOUL.md +1 -1
- package/templates/workspaces/isaac/USER.md +17 -0
- package/templates/workspaces/isaac/skills/claude-code/SKILL.md +38 -0
- package/templates/workspaces/isaac/skills/claude-code/claude_code +55 -0
- package/templates/workspaces/logan/AGENTS.md +59 -0
- package/templates/workspaces/logan/FEEDBACK.md +11 -0
- package/templates/workspaces/logan/HEARTBEAT.md +9 -0
- package/templates/workspaces/logan/IDENTITY.md +6 -0
- package/templates/workspaces/logan/LEARNINGS.md +5 -0
- package/templates/workspaces/logan/MEMORY.md +22 -0
- package/templates/workspaces/logan/PLAYBOOK.md +16 -0
- package/templates/workspaces/logan/SOUL.md +1 -1
- package/templates/workspaces/logan/TOOLS.md +15 -0
- package/templates/workspaces/logan/USER.md +13 -0
- package/templates/workspaces/maxxipro/AGENTS.md +29 -0
- package/templates/workspaces/maxxipro/FEEDBACK.md +19 -0
- package/templates/workspaces/maxxipro/HEARTBEAT.md +22 -0
- package/templates/workspaces/maxxipro/IDENTITY.md +35 -0
- package/templates/workspaces/maxxipro/KNOWLEDGE.md +335 -0
- package/templates/workspaces/maxxipro/LEARNINGS.md +47 -0
- package/templates/workspaces/maxxipro/MEMORY.md +60 -0
- package/templates/workspaces/maxxipro/OUTREACH_TEMPLATES.md +143 -0
- package/templates/workspaces/maxxipro/PLAYBOOK.md +81 -0
- package/templates/workspaces/maxxipro/SOUL.md +146 -0
- package/templates/workspaces/maxxipro/TOOLS.md +81 -0
- package/templates/workspaces/maxxipro/USER.md +40 -0
- package/templates/workspaces/morgan/AGENTS.md +59 -0
- package/templates/workspaces/morgan/FEEDBACK.md +19 -0
- package/templates/workspaces/morgan/HEARTBEAT.md +5 -0
- package/templates/workspaces/morgan/IDENTITY.md +6 -0
- package/templates/workspaces/morgan/LEARNINGS.md +18 -0
- package/templates/workspaces/morgan/MEMORY.md +22 -0
- package/templates/workspaces/morgan/PLAYBOOK.md +16 -0
- package/templates/workspaces/morgan/SOUL.md +1 -1
- package/templates/workspaces/morgan/TOOLS.md +15 -0
- package/templates/workspaces/morgan/USER.md +13 -0
- package/templates/workspaces/nadia/AGENTS.md +59 -0
- package/templates/workspaces/nadia/FEEDBACK.md +12 -0
- package/templates/workspaces/nadia/HEARTBEAT.md +5 -0
- package/templates/workspaces/nadia/IDENTITY.md +6 -0
- package/templates/workspaces/nadia/LEARNINGS.md +6 -0
- package/templates/workspaces/nadia/MEMORY.md +22 -0
- package/templates/workspaces/nadia/PLAYBOOK.md +16 -0
- package/templates/workspaces/nadia/SOUL.md +1 -1
- package/templates/workspaces/nadia/TOOLS.md +15 -0
- package/templates/workspaces/nadia/USER.md +13 -0
- package/templates/workspaces/nate/AGENTS.md +24 -0
- package/templates/workspaces/nate/FEEDBACK.md +12 -0
- package/templates/workspaces/nate/HEARTBEAT.md +33 -0
- package/templates/workspaces/nate/IDENTITY.md +15 -0
- package/templates/workspaces/nate/LEARNINGS.md +33 -0
- package/templates/workspaces/nate/MEMORY.md +39 -0
- package/templates/workspaces/nate/PLAYBOOK.md +160 -0
- package/templates/workspaces/nate/SOUL.md +50 -0
- package/templates/workspaces/nate/TOOLS.md +111 -0
- package/templates/workspaces/nate/USER.md +32 -0
- package/templates/workspaces/olivia/.last-openclaw-version +1 -0
- package/templates/workspaces/olivia/.npmrc.tmp +0 -0
- package/templates/workspaces/olivia/AGENTS.md +77 -0
- package/templates/workspaces/olivia/ALPHA_CODING_BENCHMARK.txt +148 -0
- package/templates/workspaces/olivia/ALPHA_MODEL_GUIDE.md +393 -0
- package/templates/workspaces/olivia/FEEDBACK.md +13 -0
- package/templates/workspaces/olivia/HEADTOHEAD_BENCHMARK.txt +1289 -0
- package/templates/workspaces/olivia/HEARTBEAT.md +267 -0
- package/templates/workspaces/olivia/IDENTITY.md +6 -0
- package/templates/workspaces/olivia/LEARNINGS.md +708 -0
- package/templates/workspaces/olivia/MEMORY.md +202 -0
- package/templates/workspaces/olivia/MISSION_CONTROL_DESIGN_SPEC_v1.md +1143 -0
- package/templates/workspaces/olivia/MVP-COMPLETION-SUMMARY.md +175 -0
- package/templates/workspaces/olivia/NETWORK_IMPLEMENTATION_PLAN.md +1556 -0
- package/templates/workspaces/olivia/NEW_NODES_BENCHMARK.txt +947 -0
- package/templates/workspaces/olivia/PLAYBOOK.md +42 -0
- package/templates/workspaces/olivia/SELF-HEALING-COMPLETE.md +150 -0
- package/templates/workspaces/olivia/SOUL.md +8 -8
- package/templates/workspaces/olivia/TOOLS.md +15 -0
- package/templates/workspaces/olivia/USER.md +17 -0
- package/templates/workspaces/olivia/alicefleet-supabase-credentials.md +50 -0
- package/templates/workspaces/olivia/dzombo-copy-rewrite.md +115 -0
- package/templates/workspaces/olivia/dzombo-implementation-plan.md +1248 -0
- package/templates/workspaces/olivia/fidelia-psychology.html +1594 -0
- package/templates/workspaces/olivia/lead_debug.png +0 -0
- package/templates/workspaces/olivia/minimatch-10.2.4.tgz +0 -0
- package/templates/workspaces/olivia/operation-bllm-research.md +157 -0
- package/templates/workspaces/olivia/qa-audit-mission-control-v2.md +538 -0
- package/templates/workspaces/olivia/roofmaxx_logo.svg +1 -0
- package/templates/workspaces/olivia/roofmaxx_social.jpg +0 -0
- package/templates/workspaces/olivia/skills/1password/SKILL.md +53 -0
- package/templates/workspaces/olivia/skills/1password/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/README.md +57 -0
- package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/SKILL.md +534 -0
- package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/agent-security/SKILL.md +69 -0
- package/templates/workspaces/olivia/skills/agent-security/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/agentic-security-audit/SKILL.md +855 -0
- package/templates/workspaces/olivia/skills/agentic-security-audit/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-automation-consulting/SKILL.md +67 -0
- package/templates/workspaces/olivia/skills/ai-automation-consulting/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-automation-consulting/skill.json +12 -0
- package/templates/workspaces/olivia/skills/ai-presentation-maker/SKILL.md +1104 -0
- package/templates/workspaces/olivia/skills/ai-presentation-maker/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-productivity-audit/SKILL.md +181 -0
- package/templates/workspaces/olivia/skills/ai-productivity-audit/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-researcher/README.md +31 -0
- package/templates/workspaces/olivia/skills/ai-researcher/SKILL.md +59 -0
- package/templates/workspaces/olivia/skills/ai-researcher/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/ai-seo-writer/README.md +19 -0
- package/templates/workspaces/olivia/skills/ai-seo-writer/SKILL.md +100 -0
- package/templates/workspaces/olivia/skills/ai-seo-writer/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/analytics-tracking-2/SKILL.md +309 -0
- package/templates/workspaces/olivia/skills/analytics-tracking-2/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/api-doc-writer/SKILL.md +232 -0
- package/templates/workspaces/olivia/skills/api-doc-writer/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/api-generator/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/api-generator/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/api-generator/tips.md +10 -0
- package/templates/workspaces/olivia/skills/apple-notes/SKILL.md +50 -0
- package/templates/workspaces/olivia/skills/apple-notes/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/apple-reminders/SKILL.md +67 -0
- package/templates/workspaces/olivia/skills/apple-reminders/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/automation-workflows/SKILL.md +267 -0
- package/templates/workspaces/olivia/skills/automation-workflows/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/autoresearch/SKILL.md +46 -0
- package/templates/workspaces/olivia/skills/autoresearch/aria_write.py +148 -0
- package/templates/workspaces/olivia/skills/autoresearch/autoresearch.py +75 -0
- package/templates/workspaces/olivia/skills/azure-devops/SKILL.md +115 -0
- package/templates/workspaces/olivia/skills/azure-devops/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/blogwatcher/SKILL.md +46 -0
- package/templates/workspaces/olivia/skills/blogwatcher/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/blucli/SKILL.md +27 -0
- package/templates/workspaces/olivia/skills/blucli/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/check-analytics/SKILL.md +92 -0
- package/templates/workspaces/olivia/skills/check-analytics/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-architect/SKILL.md +89 -0
- package/templates/workspaces/olivia/skills/cloud-architect/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-infra-automation/SKILL.md +50 -0
- package/templates/workspaces/olivia/skills/cloud-infra-automation/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-storage/SKILL.md +61 -0
- package/templates/workspaces/olivia/skills/cloud-storage/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/cloud-storage/auth.md +97 -0
- package/templates/workspaces/olivia/skills/cloud-storage/costs.md +88 -0
- package/templates/workspaces/olivia/skills/cloud-storage/providers.md +55 -0
- package/templates/workspaces/olivia/skills/copywriting-pro/SKILL.md +107 -0
- package/templates/workspaces/olivia/skills/copywriting-pro/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/data-analyst-pro/SKILL.md +21 -0
- package/templates/workspaces/olivia/skills/data-analyst-pro/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/database-designer/README.md +388 -0
- package/templates/workspaces/olivia/skills/database-designer/SKILL.md +66 -0
- package/templates/workspaces/olivia/skills/database-designer/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/database-designer/index_optimizer.py +926 -0
- package/templates/workspaces/olivia/skills/database-designer/migration_generator.py +1199 -0
- package/templates/workspaces/olivia/skills/database-designer/schema_analyzer.py +982 -0
- package/templates/workspaces/olivia/skills/deploy-agent/SKILL.md +255 -0
- package/templates/workspaces/olivia/skills/deploy-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/devops-automation-pack/SKILL.md +72 -0
- package/templates/workspaces/olivia/skills/devops-automation-pack/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/devops-automation-pack/deploy.sh +0 -0
- package/templates/workspaces/olivia/skills/financial-analysis-agent/SKILL.md +489 -0
- package/templates/workspaces/olivia/skills/financial-analysis-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/README.md +72 -0
- package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/SKILL.md +226 -0
- package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/gifgrep/SKILL.md +47 -0
- package/templates/workspaces/olivia/skills/gifgrep/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/github/SKILL.md +47 -0
- package/templates/workspaces/olivia/skills/github/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/gog/SKILL.md +36 -0
- package/templates/workspaces/olivia/skills/gog/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/growth-strategy-hub/SKILL.md +135 -0
- package/templates/workspaces/olivia/skills/growth-strategy-hub/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/growth-strategy-hub/metadata.json +4 -0
- package/templates/workspaces/olivia/skills/hetzner-cloud/SKILL.md +130 -0
- package/templates/workspaces/olivia/skills/hetzner-cloud/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/himalaya/SKILL.md +217 -0
- package/templates/workspaces/olivia/skills/himalaya/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/hotel-recommendation/SKILL.md +117 -0
- package/templates/workspaces/olivia/skills/hotel-recommendation/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/hr-policy-generator/SKILL.md +54 -0
- package/templates/workspaces/olivia/skills/hr-policy-generator/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/human-writing/SKILL.md +41 -0
- package/templates/workspaces/olivia/skills/human-writing/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/imsg/SKILL.md +25 -0
- package/templates/workspaces/olivia/skills/imsg/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/in-depth-research/SKILL.md +124 -0
- package/templates/workspaces/olivia/skills/in-depth-research/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/in-depth-research/methodology.md +75 -0
- package/templates/workspaces/olivia/skills/in-depth-research/output-formats.md +168 -0
- package/templates/workspaces/olivia/skills/in-depth-research/sources.md +80 -0
- package/templates/workspaces/olivia/skills/javascript-skills/README.md +71 -0
- package/templates/workspaces/olivia/skills/javascript-skills/SKILL.md +746 -0
- package/templates/workspaces/olivia/skills/javascript-skills/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/leadership-strategy-playbook/SKILL.md +147 -0
- package/templates/workspaces/olivia/skills/leadership-strategy-playbook/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/market-research-agent/README.md +29 -0
- package/templates/workspaces/olivia/skills/market-research-agent/SKILL.md +52 -0
- package/templates/workspaces/olivia/skills/market-research-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/marketing-analytics/SKILL.md +74 -0
- package/templates/workspaces/olivia/skills/marketing-analytics/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/marketing-master-io/SKILL.md +125 -0
- package/templates/workspaces/olivia/skills/marketing-master-io/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/marketing-strategy-pmm/SKILL.md +398 -0
- package/templates/workspaces/olivia/skills/marketing-strategy-pmm/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/meta-ads-analytics/SKILL.md +53 -0
- package/templates/workspaces/olivia/skills/meta-ads-analytics/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/obsidian/SKILL.md +55 -0
- package/templates/workspaces/olivia/skills/obsidian/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/openclaw-accounting/SKILL.md +125 -0
- package/templates/workspaces/olivia/skills/openclaw-accounting/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANGELOG.md +35 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANNELLOG.md +73 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/README.md +161 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/SKILL.md +130 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/config.json +36 -0
- package/templates/workspaces/olivia/skills/openclaw-security-toolkit/metadata.json +19 -0
- package/templates/workspaces/olivia/skills/openhue/SKILL.md +30 -0
- package/templates/workspaces/olivia/skills/openhue/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/orgx-operations-agent/SKILL.md +41 -0
- package/templates/workspaces/olivia/skills/orgx-operations-agent/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/outreach/SKILL.md +84 -0
- package/templates/workspaces/olivia/skills/outreach/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/outreach/by-type.md +166 -0
- package/templates/workspaces/olivia/skills/outreach/templates.md +154 -0
- package/templates/workspaces/olivia/skills/outreach/tracking.md +145 -0
- package/templates/workspaces/olivia/skills/persona-hr-coordinator/SKILL.md +38 -0
- package/templates/workspaces/olivia/skills/persona-hr-coordinator/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/personal-productivity/SKILL.md +161 -0
- package/templates/workspaces/olivia/skills/personal-productivity/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/personal-productivity/index.js +363 -0
- package/templates/workspaces/olivia/skills/personal-productivity/package.json +15 -0
- package/templates/workspaces/olivia/skills/personal-travel/README.md +34 -0
- package/templates/workspaces/olivia/skills/personal-travel/SKILL.md +46 -0
- package/templates/workspaces/olivia/skills/personal-travel/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/presentation-html-generator-skill/SKILL.md +185 -0
- package/templates/workspaces/olivia/skills/presentation-html-generator-skill/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/product-manager/SKILL.md +77 -0
- package/templates/workspaces/olivia/skills/product-manager/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/quant-strategy/SKILL.md +28 -0
- package/templates/workspaces/olivia/skills/quant-strategy/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/sales-pipeline-tracker/README.md +29 -0
- package/templates/workspaces/olivia/skills/sales-pipeline-tracker/SKILL.md +45 -0
- package/templates/workspaces/olivia/skills/sales-pipeline-tracker/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/security-auditor/SKILL.md +399 -0
- package/templates/workspaces/olivia/skills/security-auditor/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/security-hardening/SKILL.md +296 -0
- package/templates/workspaces/olivia/skills/security-hardening/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/security-scanner/SKILL.md +67 -0
- package/templates/workspaces/olivia/skills/security-scanner/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/seo-optimization/SKILL.md +31 -0
- package/templates/workspaces/olivia/skills/seo-optimization/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/service-booking/SKILL.md +193 -0
- package/templates/workspaces/olivia/skills/service-booking/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/sme-hr-automation/SKILL.md +131 -0
- package/templates/workspaces/olivia/skills/sme-hr-automation/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/social-media-scheduler/README.md +29 -0
- package/templates/workspaces/olivia/skills/social-media-scheduler/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/social-media-scheduler/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/sonoscli/SKILL.md +26 -0
- package/templates/workspaces/olivia/skills/sonoscli/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/strategy-advisor/SKILL.md +33 -0
- package/templates/workspaces/olivia/skills/strategy-advisor/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/summarize/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/summarize/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/things-mac/SKILL.md +61 -0
- package/templates/workspaces/olivia/skills/things-mac/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-itinerary-planner/SKILL.md +121 -0
- package/templates/workspaces/olivia/skills/travel-itinerary-planner/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-manager/SKILL.md +36 -0
- package/templates/workspaces/olivia/skills/travel-manager/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-planning/SKILL.md +238 -0
- package/templates/workspaces/olivia/skills/travel-planning/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/travel-planning/booking-guide.md +91 -0
- package/templates/workspaces/olivia/skills/travel-planning/memory-template.md +111 -0
- package/templates/workspaces/olivia/skills/travel-planning/multi-city.md +131 -0
- package/templates/workspaces/olivia/skills/travel-planning/packing-templates.md +155 -0
- package/templates/workspaces/olivia/skills/travel-planning/setup.md +66 -0
- package/templates/workspaces/olivia/skills/update-it-all/SKILL.md +143 -0
- package/templates/workspaces/olivia/skills/update-it-all/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/voice/SKILL.md +62 -0
- package/templates/workspaces/olivia/skills/weather/SKILL.md +49 -0
- package/templates/workspaces/olivia/skills/weather/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/web-researcher/SKILL.md +21 -0
- package/templates/workspaces/olivia/skills/web-researcher/_meta.json +6 -0
- package/templates/workspaces/olivia/skills/website-seo/SKILL.md +284 -0
- package/templates/workspaces/olivia/skills/website-seo/_meta.json +6 -0
- package/templates/workspaces/olivia/stripe-welcome-n8n.json +103 -0
- package/templates/workspaces/olivia/test2.wav.wav +0 -0
- package/templates/workspaces/olivia/test_speech.json +1 -0
- package/templates/workspaces/olivia/test_speech.srt +0 -0
- package/templates/workspaces/olivia/test_speech.tsv +1 -0
- package/templates/workspaces/olivia/test_speech.txt +0 -0
- package/templates/workspaces/olivia/test_speech.vtt +2 -0
- package/templates/workspaces/owen/AGENTS.md +59 -0
- package/templates/workspaces/owen/FEEDBACK.md +12 -0
- package/templates/workspaces/owen/HEARTBEAT.md +5 -0
- package/templates/workspaces/owen/IDENTITY.md +6 -0
- package/templates/workspaces/owen/LEARNINGS.md +46 -0
- package/templates/workspaces/owen/MEMORY.md +22 -0
- package/templates/workspaces/owen/PLAYBOOK.md +16 -0
- package/templates/workspaces/owen/SOUL.md +1 -1
- package/templates/workspaces/owen/TOOLS.md +15 -0
- package/templates/workspaces/owen/USER.md +17 -0
- package/templates/workspaces/parker/AGENTS.md +59 -0
- package/templates/workspaces/parker/FEEDBACK.md +11 -0
- package/templates/workspaces/parker/HEARTBEAT.md +5 -0
- package/templates/workspaces/parker/IDENTITY.md +6 -0
- package/templates/workspaces/parker/LEARNINGS.md +17 -0
- package/templates/workspaces/parker/MEMORY.md +22 -0
- package/templates/workspaces/parker/PLAYBOOK.md +16 -0
- package/templates/workspaces/parker/SOUL.md +1 -1
- package/templates/workspaces/parker/TOOLS.md +15 -0
- package/templates/workspaces/parker/USER.md +13 -0
- package/templates/workspaces/quinn/AGENTS.md +52 -0
- package/templates/workspaces/quinn/FEEDBACK.md +11 -0
- package/templates/workspaces/quinn/HEARTBEAT.md +5 -0
- package/templates/workspaces/quinn/IDENTITY.md +6 -0
- package/templates/workspaces/quinn/LEARNINGS.md +35 -0
- package/templates/workspaces/quinn/MEMORY.md +22 -0
- package/templates/workspaces/quinn/PLAYBOOK.md +16 -0
- package/templates/workspaces/quinn/SOUL.md +1 -1
- package/templates/workspaces/quinn/USER.md +17 -0
- package/templates/workspaces/quinn/alice-login-page.png +0 -0
- package/templates/workspaces/rowan/AGENTS.md +59 -0
- package/templates/workspaces/rowan/FEEDBACK.md +12 -0
- package/templates/workspaces/rowan/HEARTBEAT.md +5 -0
- package/templates/workspaces/rowan/IDENTITY.md +6 -0
- package/templates/workspaces/rowan/LEARNINGS.md +12 -0
- package/templates/workspaces/rowan/MEMORY.md +22 -0
- package/templates/workspaces/rowan/PLAYBOOK.md +16 -0
- package/templates/workspaces/rowan/SOUL.md +1 -1
- package/templates/workspaces/rowan/USER.md +17 -0
- package/templates/workspaces/selena/AGENTS.md +59 -0
- package/templates/workspaces/selena/FEEDBACK.md +12 -0
- package/templates/workspaces/selena/HEARTBEAT.md +5 -0
- package/templates/workspaces/selena/IDENTITY.md +6 -0
- package/templates/workspaces/selena/LEARNINGS.md +24 -0
- package/templates/workspaces/selena/MEMORY.md +22 -0
- package/templates/workspaces/selena/PLAYBOOK.md +16 -0
- package/templates/workspaces/selena/SOUL.md +1 -1
- package/templates/workspaces/selena/USER.md +17 -0
- package/templates/workspaces/selena/kids-ai-security-compliance-plan.md +791 -0
- package/templates/workspaces/selena/kidspark-coppa-compliance-audit.md +866 -0
- package/templates/workspaces/sloane/AGENTS.md +59 -0
- package/templates/workspaces/sloane/FEEDBACK.md +12 -0
- package/templates/workspaces/sloane/HEARTBEAT.md +9 -0
- package/templates/workspaces/sloane/IDENTITY.md +6 -0
- package/templates/workspaces/sloane/LEARNINGS.md +6 -0
- package/templates/workspaces/sloane/MEMORY.md +22 -0
- package/templates/workspaces/sloane/PLAYBOOK.md +16 -0
- package/templates/workspaces/sloane/SOUL.md +1 -1
- package/templates/workspaces/sloane/TOOLS.md +15 -0
- package/templates/workspaces/sloane/USER.md +13 -0
- package/templates/workspaces/smoketestagent/AGENTS.md +52 -0
- package/templates/workspaces/smoketestagent/FEEDBACK.md +3 -0
- package/templates/workspaces/smoketestagent/HEARTBEAT.md +14 -0
- package/templates/workspaces/smoketestagent/IDENTITY.md +6 -0
- package/templates/workspaces/smoketestagent/LEARNINGS.md +3 -0
- package/templates/workspaces/smoketestagent/MEMORY.md +24 -0
- package/templates/workspaces/smoketestagent/PLAYBOOK.md +7 -0
- package/templates/workspaces/smoketestagent/SOUL.md +32 -0
- package/templates/workspaces/smoketestagent/TOOLS.md +13 -0
- package/templates/workspaces/smoketestagent/USER.md +5 -0
- package/templates/workspaces/sophie/AGENTS.md +59 -0
- package/templates/workspaces/sophie/FEEDBACK.md +12 -0
- package/templates/workspaces/sophie/HEARTBEAT.md +9 -0
- package/templates/workspaces/sophie/IDENTITY.md +6 -0
- package/templates/workspaces/sophie/LEARNINGS.md +6 -0
- package/templates/workspaces/sophie/MEMORY.md +22 -0
- package/templates/workspaces/sophie/PLAYBOOK.md +16 -0
- package/templates/workspaces/sophie/SOUL.md +1 -1
- package/templates/workspaces/sophie/TOOLS.md +15 -0
- package/templates/workspaces/sophie/USER.md +17 -0
- package/templates/workspaces/tommy/AGENTS.md +59 -0
- package/templates/workspaces/tommy/FEEDBACK.md +12 -0
- package/templates/workspaces/tommy/HEARTBEAT.md +9 -0
- package/templates/workspaces/tommy/IDENTITY.md +6 -0
- package/templates/workspaces/tommy/LEARNINGS.md +6 -0
- package/templates/workspaces/tommy/MEMORY.md +22 -0
- package/templates/workspaces/tommy/PLAYBOOK.md +16 -0
- package/templates/workspaces/tommy/SOUL.md +1 -1
- package/templates/workspaces/tommy/TOOLS.md +15 -0
- package/templates/workspaces/tommy/USER.md +17 -0
- package/templates/workspaces/uma/AGENTS.md +59 -0
- package/templates/workspaces/uma/FEEDBACK.md +11 -0
- package/templates/workspaces/uma/HEARTBEAT.md +5 -0
- package/templates/workspaces/uma/IDENTITY.md +6 -0
- package/templates/workspaces/uma/LEARNINGS.md +11 -0
- package/templates/workspaces/uma/MEMORY.md +22 -0
- package/templates/workspaces/uma/PLAYBOOK.md +16 -0
- package/templates/workspaces/uma/SOUL.md +1 -1
- package/templates/workspaces/uma/TOOLS.md +15 -0
- package/templates/workspaces/uma/USER.md +13 -0
|
@@ -0,0 +1,1289 @@
|
|
|
1
|
+
HEAD-TO-HEAD: Apple Silicon (Mac Studio M-series) vs NVIDIA Orin (AGX 64GB)
|
|
2
|
+
Task: Thread-safe LRU Cache with per-key TTL
|
|
3
|
+
Date: Mon Mar 23 10:22:10 EDT 2026
|
|
4
|
+
Models tested: Only those present on BOTH nodes at equivalent size
|
|
5
|
+
==========================================
|
|
6
|
+
|
|
7
|
+
=== GROUP 1: lfm2:24b ===
|
|
8
|
+
|
|
9
|
+
==========================================
|
|
10
|
+
MODEL: alpha-orin/lfm2:24b
|
|
11
|
+
==========================================
|
|
12
|
+
LOAD: 29.6s | TOKENS: 1500 | DURATION: 37.7s | SPEED: 39.8 tok/s
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
"""
|
|
16
|
+
Thread-safe LRU cache with per-key TTL expiration.
|
|
17
|
+
|
|
18
|
+
Implementation:
|
|
19
|
+
- Uses a doubly linked list (head/tail) + hashmap (dict) for O(1) access.
|
|
20
|
+
- Each entry stores: value, expiration timestamp (None if no TTL).
|
|
21
|
+
- Thread-safety via `threading.Lock`.
|
|
22
|
+
- Expired entries are removed lazily on `get`/`put`.
|
|
23
|
+
- Full type hints and docstrings provided.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import time
|
|
27
|
+
import threading
|
|
28
|
+
from typing import Any, Dict, Optional
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _Node:
|
|
32
|
+
"""
|
|
33
|
+
Doubly-linked list node storing key, value, and expiration timestamp.
|
|
34
|
+
"""
|
|
35
|
+
__slots__ = ("key", "value", "expire_at", "prev", "next")
|
|
36
|
+
|
|
37
|
+
def __init__(self, key: Any, value: Any, expire_at: Optional[float]):
|
|
38
|
+
self.key = key
|
|
39
|
+
self.value = value
|
|
40
|
+
self.expire_at = expire_at # None if no TTL
|
|
41
|
+
self.prev: Optional["_Node"] = None
|
|
42
|
+
self.next: Optional["_Node"] = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class LRUCache:
|
|
46
|
+
"""
|
|
47
|
+
Thread-safe LRU cache with per-key TTL expiration.
|
|
48
|
+
|
|
49
|
+
Attributes
|
|
50
|
+
----------
|
|
51
|
+
capacity : int
|
|
52
|
+
Maximum number of items stored.
|
|
53
|
+
cache : Dict[Any, _Node]
|
|
54
|
+
Mapping from key to its node in the linked list.
|
|
55
|
+
head : _Node
|
|
56
|
+
tail : _Node
|
|
57
|
+
lock : threading.Lock
|
|
58
|
+
Protects all cache operations.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, capacity: int = 128) -> None:
|
|
62
|
+
if capacity <= 0:
|
|
63
|
+
raise ValueError("capacity must be positive")
|
|
64
|
+
self.capacity = capacity
|
|
65
|
+
self.cache: Dict[Any, _Node] = {}
|
|
66
|
+
self.head: Optional[_Node] = None
|
|
67
|
+
self.tail: Optional[_Node] = None
|
|
68
|
+
self.lock = threading.Lock()
|
|
69
|
+
|
|
70
|
+
# --------------------------------------------------------------------- #
|
|
71
|
+
# Internal helpers
|
|
72
|
+
# --------------------------------------------------------------------- #
|
|
73
|
+
def _remove(self, node: _Node) -> None:
|
|
74
|
+
"""Detach node from the linked list."""
|
|
75
|
+
if node.prev:
|
|
76
|
+
node.prev.next = node.next
|
|
77
|
+
else:
|
|
78
|
+
self.head = node.next # node was head
|
|
79
|
+
if node.next:
|
|
80
|
+
node.next.prev = node.prev
|
|
81
|
+
else:
|
|
82
|
+
self.tail = node.prev # node was tail
|
|
83
|
+
|
|
84
|
+
def _add_to_head(self, node: _Node) -> None:
|
|
85
|
+
"""Insert node right after head."""
|
|
86
|
+
node.prev = self.head
|
|
87
|
+
node.next = self.head.next if self.head else None
|
|
88
|
+
if self.head:
|
|
89
|
+
self.head.next.prev = node
|
|
90
|
+
self.head = node
|
|
91
|
+
if not self.tail:
|
|
92
|
+
self.tail = node
|
|
93
|
+
|
|
94
|
+
def _evict_expired(self) -> None:
|
|
95
|
+
"""Remove expired entries lazily."""
|
|
96
|
+
now = time.time()
|
|
97
|
+
node = self.tail
|
|
98
|
+
while node:
|
|
99
|
+
if node.expire_at is None or node.expire_at > now:
|
|
100
|
+
break
|
|
101
|
+
# Expired
|
|
102
|
+
self.cache.pop(node.key, None)
|
|
103
|
+
node = node.prev # move backward (oldest)
|
|
104
|
+
|
|
105
|
+
# --------------------------------------------------------------------- #
|
|
106
|
+
# Public API
|
|
107
|
+
# --------------------------------------------------------------------- #
|
|
108
|
+
def get(self, key: Any) -> Optional[Any]:
|
|
109
|
+
"""
|
|
110
|
+
Retrieve value by key. Returns None if not present or expired.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
key : Any
|
|
115
|
+
The key to look up.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
Optional[Any]
|
|
120
|
+
Value if found and not expired, else None.
|
|
121
|
+
"""
|
|
122
|
+
with self.lock:
|
|
123
|
+
node = self.cache.get(key)
|
|
124
|
+
if node is None:
|
|
125
|
+
return None
|
|
126
|
+
# Expired?
|
|
127
|
+
if node.expire_at is not None and node.expire_at <= time.time():
|
|
128
|
+
# Remove expired entry
|
|
129
|
+
self._remove(node)
|
|
130
|
+
self.cache.pop(key, None)
|
|
131
|
+
return None
|
|
132
|
+
# Not expired; move to head (most recently used)
|
|
133
|
+
self._remove(node)
|
|
134
|
+
self._add_to_head(node)
|
|
135
|
+
return node.value
|
|
136
|
+
|
|
137
|
+
def put(self, key: Any, value: Any, ttl_seconds: Optional[float] = None) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Insert or update key-value pair. Optionally set TTL (seconds).
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
key : Any
|
|
144
|
+
The key to store.
|
|
145
|
+
value : Any
|
|
146
|
+
Value to associate.
|
|
147
|
+
ttl_seconds : Optional[float], default None
|
|
148
|
+
Time-to-live in seconds. If None, entry never expires.
|
|
149
|
+
"""
|
|
150
|
+
with self.lock:
|
|
151
|
+
now = time.time()
|
|
152
|
+
expire_at = now + ttl_seconds if ttl_seconds is not None else None
|
|
153
|
+
node = self.cache.get(key)
|
|
154
|
+
if node:
|
|
155
|
+
# Update value and expiration
|
|
156
|
+
node.value = value
|
|
157
|
+
node.expire_at = expire_at
|
|
158
|
+
# Move to head
|
|
159
|
+
self._remove(node)
|
|
160
|
+
self._add_to_head(node)
|
|
161
|
+
else:
|
|
162
|
+
# New node
|
|
163
|
+
node = _Node(key, value, expire_at)
|
|
164
|
+
self.cache[key] = node
|
|
165
|
+
self._add_to_head(node)
|
|
166
|
+
# Evict if necessary
|
|
167
|
+
if len(self.cache) > self.capacity:
|
|
168
|
+
self._evict_expired()
|
|
169
|
+
if len(self.cache) > self.capacity:
|
|
170
|
+
# Tail is now expired or already removed
|
|
171
|
+
self._evict_expired()
|
|
172
|
+
if self.tail:
|
|
173
|
+
self._remove(self.tail)
|
|
174
|
+
self.cache.pop(self.tail.key, None)
|
|
175
|
+
self._evict_expired()
|
|
176
|
+
# Ensure expire_at is set correctly for new node
|
|
177
|
+
node.expire_at = expire_at
|
|
178
|
+
|
|
179
|
+
def delete(self, key: Any) -> None:
|
|
180
|
+
"""
|
|
181
|
+
Remove key from cache.
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
key : Any
|
|
186
|
+
The key to delete.
|
|
187
|
+
"""
|
|
188
|
+
with self.lock:
|
|
189
|
+
node = self.cache.pop(key, None)
|
|
190
|
+
if node:
|
|
191
|
+
self._remove(node)
|
|
192
|
+
# No need to evict further; tail/head will adjust on next access
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# -------------------------------------------------------------------------
|
|
196
|
+
# Example usage
|
|
197
|
+
# -------------------------------------------------------------------------
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
import threading
|
|
200
|
+
import time
|
|
201
|
+
|
|
202
|
+
cache = LRUCache(capacity=3)
|
|
203
|
+
|
|
204
|
+
def worker(id: int):
|
|
205
|
+
for i in range(5):
|
|
206
|
+
cache.put(f"key-{id}-{i}", f"value-{id}-{i}", ttl_seconds=2)
|
|
207
|
+
print(f
|
|
208
|
+
|
|
209
|
+
==========================================
|
|
210
|
+
MODEL: mac-studio/lfm2:24b
|
|
211
|
+
==========================================
|
|
212
|
+
ERROR: model 'lfm2:24b' not found
|
|
213
|
+
|
|
214
|
+
=== GROUP 2: deepseek-r1:14b ===
|
|
215
|
+
|
|
216
|
+
==========================================
|
|
217
|
+
MODEL: alpha-orin/deepseek-r1:14b
|
|
218
|
+
==========================================
|
|
219
|
+
LOAD: 22.0s | TOKENS: 1500 | DURATION: 109.7s | SPEED: 13.7 tok/s
|
|
220
|
+
|
|
221
|
+
Okay, I need to implement a thread-safe LRU cache in Python with per-key TTL expiration. The requirements specify using a doubly linked list plus hashmap internally, so I can't use OrderedDict or functools.lru_cache. Hmm, let's think about how to structure this.
|
|
222
|
+
|
|
223
|
+
First, the main components are the hashmap for O(1) access and the linked list to keep track of the order of recently used items. Each node in the linked list will represent a key-value pair along with its TTL information.
|
|
224
|
+
|
|
225
|
+
I should define a Node class that has prev and next pointers, as well as attributes for key, value, ttl, and last_accessed_time. The hashmap will map keys to these nodes.
|
|
226
|
+
|
|
227
|
+
The cache needs methods: get(key), put(key, value, ttl_seconds), delete(key). Also, it must be thread-safe using threading.Lock. So I'll need a lock that's acquired before any modification or access that could cause race conditions.
|
|
228
|
+
|
|
229
|
+
For the TTL handling, each key has its own expiration time. When a key is accessed (get or put), its last_accessed_time is updated. If during get or accessing the linked list for eviction a key's ttl has expired, it should be removed from both the hashmap and the linked list.
|
|
230
|
+
|
|
231
|
+
Expire logic should happen lazily, meaning that we don't check every key on every access but only when necessary, like when a new element is added beyond capacity or during an access. So in get, I'll first check if the key exists, then verify its TTL; if expired, remove it and return None.
|
|
232
|
+
|
|
233
|
+
For the linked list operations: inserting at the front (when accessed), removing from anywhere, and handling the head and tail pointers correctly. Moving a node to the front when it's accessed again involves adjusting the prev and next pointers of the surrounding nodes.
|
|
234
|
+
|
|
235
|
+
In the put method, after adding the key, if the size exceeds the maximum capacity, I need to evict the least recently used item, which is at the end of the linked list (the tail). So I'll check each node from the tail backwards until I find one that's expired or has a ttl older than its last_accessed time.
|
|
236
|
+
|
|
237
|
+
Wait, no. Actually, in an LRU cache, the order determines recency. When evicting, the node at the end is the least recently used, regardless of TTL, unless it's already expired. So first, I check if the tail node is expired; if so, remove it and continue until a non-expired node is found or the list becomes empty.
|
|
238
|
+
|
|
239
|
+
For thread-safety, all public methods (get, put, delete) must acquire the lock before performing any operations on the data structures.
|
|
240
|
+
|
|
241
|
+
Now, about type hints and docstrings. I'll need to import typing for Optional, Union, Any. Each method should have a clear docstring explaining its parameters and return values.
|
|
242
|
+
|
|
243
|
+
Let me outline the steps:
|
|
244
|
+
|
|
245
|
+
1. Create the Node class with __init__, methods to set prev/next, etc.
|
|
246
|
+
2. Implement the LRUCache class with __init__ setting up lock, capacity, head, tail, hashmap.
|
|
247
|
+
3. get(key): acquire lock, check if key exists and not expired. If so, move to front of linked list, update last_accessed_time, return value. Else, remove from hashmap and linked list.
|
|
248
|
+
4. put(key, value, ttl): acquire lock, check if key exists; if so, update value, ttl, and last_accessed_time. Else, create new node. Add to head, update hashmap. If size exceeds capacity, evict the tail node until within capacity.
|
|
249
|
+
5. delete(key): acquire lock, remove from hashmap and linked list.
|
|
250
|
+
|
|
251
|
+
Wait, in put(), if the key exists, we should move it to the front as if accessed. Otherwise, add a new node at the front.
|
|
252
|
+
|
|
253
|
+
In the eviction process (when putting and exceeding capacity), we need to traverse from tail backward to find the first non-expired node to remove. Or perhaps just check each node starting from tail, see if it's expired or not; if so, remove it until within capacity.
|
|
254
|
+
|
|
255
|
+
Wait no: when evicting due to size, since the cache is full, we must remove nodes regardless of TTL? No, because according to LRU, the least recently used item is removed first. But since TTL is per key, a key might have expired but not been accessed yet. So during eviction, should I check if it's expired and only remove non-expired ones?
|
|
256
|
+
|
|
257
|
+
No, in this case, the LRU policy is based on access order, regardless of TTL. The TTL just determines when to automatically evict, which can be done during any operation.
|
|
258
|
+
|
|
259
|
+
So when the cache is full, we must evict the least recently used item (the tail). But before evicting, we should check if it's expired; if so, perhaps delete more than one node until within capacity.
|
|
260
|
+
|
|
261
|
+
Alternatively, maybe in the eviction process, I should look for the first non-expired node and remove that. Wait, no: because even an expired node is part of the LRU order. So during eviction when full, regardless of TTL, we evict the tail (LRU) item. However, if that item has already expired, it's possible that other items are also expired but haven't been accessed yet.
|
|
262
|
+
|
|
263
|
+
But since TTL expiration is handled lazily, maybe I should just remove the LRU item even if it's expired because in normal operation, during get or put, the TTL is checked and expired nodes are removed then. So when evicting due to size, perhaps it's better to remove the tail node regardless of whether it's expired.
|
|
264
|
+
|
|
265
|
+
Wait, no. Because if a key has an expiration time that's already passed, but hasn't been accessed yet, it would still be present in the cache until it's either accessed or the next eviction occurs.
|
|
266
|
+
|
|
267
|
+
Hmm, this is getting complicated. Let me think: when evicting because we're adding a new item and have exceeded capacity, I should check the tail node (the LRU) and see if it's expired. If it is, remove it. But maybe other nodes are also expired but are not the LRU. So perhaps during eviction, we only remove one node at a time.
|
|
268
|
+
|
|
269
|
+
No, perhaps during eviction for size, regardless of TTL, I just evict the tail node. Because even if it's expired, it's still part of the cache and its expiration is handled lazily upon next access.
|
|
270
|
+
|
|
271
|
+
So in put(), after adding the new node to the head, check if size > capacity. While size > capacity and (tail is None or tail's ttl <= last_accessed_time), then remove tail node from linked list and delete from hashmap. But wait, for each tail node, I should check whether it's expired.
|
|
272
|
+
|
|
273
|
+
Wait no: during eviction, even if a node is expired, we have to evict it because otherwise the cache is full, but perhaps there are more nodes that are not yet expired. No, in an LRU cache, when you need to remove one item, you remove the least recently used (the tail). So regardless of TTL, the tail is removed first.
|
|
274
|
+
|
|
275
|
+
So perhaps in put(), after adding the new node and increasing the size beyond capacity, I enter a loop where I check if the tail exists. If it doesn't, break. Then check
|
|
276
|
+
|
|
277
|
+
==========================================
|
|
278
|
+
MODEL: mac-studio/deepseek-r1:14b
|
|
279
|
+
==========================================
|
|
280
|
+
ERROR: model 'deepseek-r1:14b' not found
|
|
281
|
+
|
|
282
|
+
=== GROUP 3: llama3.3:70b (Alpha solo — too big for Studio) ===
|
|
283
|
+
|
|
284
|
+
==========================================
|
|
285
|
+
MODEL: alpha-orin/qwen3.5:35b
|
|
286
|
+
==========================================
|
|
287
|
+
LOAD: 52.1s | TOKENS: 1500 | DURATION: 62.4s | SPEED: 24.0 tok/s
|
|
288
|
+
|
|
289
|
+
Here's a thinking process that leads to the suggested implementation:
|
|
290
|
+
|
|
291
|
+
1. **Understand the Goal:** The objective is to implement a thread-safe Least Recently Used (LRU) cache with Time-To-Live (TTL) support in Python.
|
|
292
|
+
* **Core Constraints:** No built-in `OrderedDict` or `functools.lru_cache`. Must use a doubly linked list and a dictionary (hashmap).
|
|
293
|
+
* **Thread Safety:** Must use `threading.Lock`.
|
|
294
|
+
* **Eviction Policy:** LRU eviction.
|
|
295
|
+
* **Expiration Policy:** Lazy eviction (check expiry only when accessed or added, though standard LRU with TTL usually implies checking upon access for efficiency, sometimes a background thread is used for proactive eviction, but the prompt specifies "Expired keys are lazily evicted on access").
|
|
296
|
+
* **API:** `get(key)`, `put(key, value, ttl_seconds)`, `delete(key)`.
|
|
297
|
+
* **Quality:** Full type hints, docstrings, production-quality code (error handling, documentation), and a working usage example.
|
|
298
|
+
|
|
299
|
+
2. **Design the Data Structures:**
|
|
300
|
+
* **Node:** Represents a single entry in the cache. It needs to store `key`, `value`, and `ttl` (or expiration time). It also needs `prev` and `next` pointers for the doubly linked list.
|
|
301
|
+
* **LinkedList:** To maintain the order of usage. The head is usually the most recently used (or least, depending on convention).
|
|
302
|
+
* *Convention:* Let's say `head` points to the *most recently used* node. When `get` or `put` happens, move node to `head`. When capacity is reached, evict the node at `tail` (least recently used).
|
|
303
|
+
* **Dict:** Maps `key` to the `Node` object for $O(1)$ access.
|
|
304
|
+
* **Lock:** A standard `threading.Lock` instance to protect the state.
|
|
305
|
+
|
|
306
|
+
3. **Design the Expiration Logic:**
|
|
307
|
+
* Need to store `expiration_time` (current timestamp + TTL) in the Node.
|
|
308
|
+
* "Lazy eviction on access": When `get(key)` or `put(key)` (update) occurs, check if `current_time >= node.expiration_time`. If yes, remove the node (unlink from list and delete from dict) and return `None`.
|
|
309
|
+
* *Edge Case:* What if `put` is called on a key that already exists and hasn't expired? Update value, update TTL, move to head.
|
|
310
|
+
* *Edge Case:* What if `put` is called on an expired key? Overwrite with new TTL and move to head.
|
|
311
|
+
|
|
312
|
+
4. **Refining the Node Structure:**
|
|
313
|
+
```python
|
|
314
|
+
class _Node:
|
|
315
|
+
def __init__(self, key, value, expiration_time):
|
|
316
|
+
self.key = key
|
|
317
|
+
self.value = value
|
|
318
|
+
self.expiration_time = expiration_time
|
|
319
|
+
self.prev = None
|
|
320
|
+
self.next = None
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
5. **Refining the LRU Cache Class:**
|
|
324
|
+
* **Initialization:** `__init__(self, capacity: int, default_ttl: float)`.
|
|
325
|
+
* *Wait*, does TTL apply to every item or is there a default? The prompt says `put(key, value, ttl_seconds)`. This implies TTL is passed per item. So, class initialization needs a `capacity`. Default TTL can be global or optional, but the prompt specifically asks for `ttl_seconds` in the put method signature. Let's make the cache have a max capacity.
|
|
326
|
+
* Should the cache be bounded by count or TTL? LRU implies bounded by count. TTL implies expiration.
|
|
327
|
+
* **Methods:**
|
|
328
|
+
* `_remove_node(node)`: Unlinks node from list.
|
|
329
|
+
* `_add_node_to_head(node)`: Inserts node at head.
|
|
330
|
+
* `_evict_lru()`: Removes tail node if capacity exceeded.
|
|
331
|
+
* `_check_and_evict_expired(key)`: Helper to handle lazy eviction logic before returning value.
|
|
332
|
+
* **Thread Safety:** All read/write operations accessing shared state (`_cache`, `_head`, `_tail`) must acquire the lock.
|
|
333
|
+
|
|
334
|
+
6. **Addressing Specific Requirements:**
|
|
335
|
+
* *No OrderedDict/LRU Cache:* Check. Custom `Node` and `dict` implementation.
|
|
336
|
+
* *Thread Safety:* Check. `with self._lock:`.
|
|
337
|
+
* *Lazy Eviction:* Check. `get` checks time, `put` checks time.
|
|
338
|
+
* *Type Hints:* Check. Use `typing.Optional`, `typing.Dict`, `typing.Any` (or better, `Any` or `Hashable`).
|
|
339
|
+
* *Docstrings:* Check.
|
|
340
|
+
* *Usage Example:* Check.
|
|
341
|
+
|
|
342
|
+
7. **Drafting the Code (Mental or Scratchpad):**
|
|
343
|
+
|
|
344
|
+
* *Time:* Use `time.time()` for expiration checks.
|
|
345
|
+
* *Capactiy:* Needs to be enforced during `put`.
|
|
346
|
+
* *Delete:* Should also check expiration? Usually `delete` forces removal regardless of state, but for consistency, it's better to just remove. If it's expired, it's already a candidate for lazy eviction, but explicit delete should work even if expired (no-op is fine).
|
|
347
|
+
|
|
348
|
+
* *Handling `put` on existing key:*
|
|
349
|
+
* Look up key.
|
|
350
|
+
* If found: Check if expired. If expired -> Evict -> Proceed as insert. If valid -> Update value, update TTL, move to head.
|
|
351
|
+
* If not found: Create node. Check capacity. Evict tail if needed. Add to head.
|
|
352
|
+
|
|
353
|
+
* *Handling `get`:*
|
|
354
|
+
* Look up key.
|
|
355
|
+
* If found: Check if expired. If expired -> Remove -> Return None. If valid -> Move to head -> Return value.
|
|
356
|
+
* If not found: Return None.
|
|
357
|
+
|
|
358
|
+
* *Handling `delete`:*
|
|
359
|
+
* Look up key.
|
|
360
|
+
* If found: Remove -> Return True.
|
|
361
|
+
* Else: Return False.
|
|
362
|
+
|
|
363
|
+
8. **Refining the "Lazy Eviction":**
|
|
364
|
+
* The prompt says "Expired keys are lazily evicted on access".
|
|
365
|
+
* Does `put` count as access? Yes.
|
|
366
|
+
* Should we evict the LRU node if it happens to be expired *without* accessing it? No, that would require a proactive scanner. Lazy means we don't clean up until we touch the item.
|
|
367
|
+
* *Conflict:* If I need to make space for a new item, and the item at the tail is expired, should I evict it? Yes, that's a safe optimization, but technically lazy eviction applies to the item being accessed. For the LRU eviction logic, it is standard to remove the tail. If the tail is expired, removing it is safe. If the tail is not expired, removing it is standard
|
|
368
|
+
|
|
369
|
+
==========================================
|
|
370
|
+
MODEL: mac-studio/qwen3.5:35b
|
|
371
|
+
==========================================
|
|
372
|
+
LOAD: 10.2s | TOKENS: 1500 | DURATION: 36.4s | SPEED: 41.2 tok/s
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
import time
|
|
376
|
+
from typing import Optional, Tuple
|
|
377
|
+
from collections import OrderedDict # Used only for internal tracking of TTL metadata, not for LRU logic
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class LRUCacheNode:
|
|
381
|
+
"""Doubly linked list node for LRU cache."""
|
|
382
|
+
|
|
383
|
+
__slots__ = ['key', 'value', 'ttl_seconds', 'expire_at', 'prev', 'next']
|
|
384
|
+
|
|
385
|
+
def __init__(self, key, value: Optional[str], ttl_seconds: float):
|
|
386
|
+
self.key = key
|
|
387
|
+
self.value = value
|
|
388
|
+
self.ttl_seconds = ttl_seconds
|
|
389
|
+
self.expire_at = time.time() + ttl_seconds if ttl_seconds > 0 else float('inf')
|
|
390
|
+
self.prev = None
|
|
391
|
+
self.next = None
|
|
392
|
+
|
|
393
|
+
def is_expired(self) -> bool:
|
|
394
|
+
"""Check if this node's entry has expired."""
|
|
395
|
+
return time.time() >= self.expire_at
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class LRUCacheWithTTL:
|
|
399
|
+
"""
|
|
400
|
+
A thread-safe LRU cache with per-key Time-To-Live (TTL) expiration.
|
|
401
|
+
|
|
402
|
+
This implementation uses a doubly linked list for O(1) LRU ordering and a
|
|
403
|
+
dictionary for O(1) key lookups, without relying on OrderedDict or functools.lru_cache.
|
|
404
|
+
|
|
405
|
+
Thread Safety: All public methods are synchronized using threading.Lock.
|
|
406
|
+
Expiration: Expired keys are lazily evicted on access (get/put operations).
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
def __init__(self, max_size: int):
|
|
410
|
+
"""
|
|
411
|
+
Initialize the LRU cache with TTL support.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
max_size: Maximum number of key-value pairs the cache can hold.
|
|
415
|
+
Must be a positive integer.
|
|
416
|
+
"""
|
|
417
|
+
if not isinstance(max_size, int) or max_size <= 0:
|
|
418
|
+
raise ValueError("max_size must be a positive integer")
|
|
419
|
+
|
|
420
|
+
self.max_size = max_size
|
|
421
|
+
self._lock = __import__('threading').Lock()
|
|
422
|
+
|
|
423
|
+
# Map from key to cache node for O(1) access
|
|
424
|
+
self._node_map: dict[object, LRUCacheNode] = {}
|
|
425
|
+
|
|
426
|
+
# Doubly linked list: head = most recently used, tail = least recently used
|
|
427
|
+
self._head = LRUCacheNode(None, None, 0) # Dummy head node
|
|
428
|
+
self._tail = LRUCacheNode(None, None, 0) # Dummy tail node
|
|
429
|
+
self._head.next = self._tail
|
|
430
|
+
self._tail.prev = self._head
|
|
431
|
+
|
|
432
|
+
def _remove_node(self, node: LRUCacheNode) -> None:
|
|
433
|
+
"""Remove a node from the doubly linked list."""
|
|
434
|
+
node.prev.next = node.next
|
|
435
|
+
node.next.prev = node.prev
|
|
436
|
+
node.prev = None
|
|
437
|
+
node.next = None
|
|
438
|
+
|
|
439
|
+
def _add_to_head(self, node: LRUCacheNode) -> None:
|
|
440
|
+
"""Add a node to the front of the doubly linked list (most recently used)."""
|
|
441
|
+
node.next = self._head.next
|
|
442
|
+
node.prev = self._head
|
|
443
|
+
self._head.next.prev = node
|
|
444
|
+
self._head.next = node
|
|
445
|
+
|
|
446
|
+
def _evict_lru(self) -> None:
|
|
447
|
+
"""Remove the least recently used entry (at tail)."""
|
|
448
|
+
if len(self._node_map) >= self.max_size and self._tail.prev != self._head:
|
|
449
|
+
lru_node = self._tail.prev
|
|
450
|
+
self._remove_node(lru_node)
|
|
451
|
+
del self._node_map[lru_node.key]
|
|
452
|
+
|
|
453
|
+
def _touch_key(self, node: LRUCacheNode) -> bool:
|
|
454
|
+
"""Move a node to head and check if expired."""
|
|
455
|
+
if node.is_expired():
|
|
456
|
+
return False
|
|
457
|
+
self._remove_node(node)
|
|
458
|
+
self._add_to_head(node)
|
|
459
|
+
return True
|
|
460
|
+
|
|
461
|
+
def get(self, key: object) -> Optional[str]:
|
|
462
|
+
"""
|
|
463
|
+
Retrieve the value associated with a key.
|
|
464
|
+
|
|
465
|
+
If the key is expired or not present, returns None and evicts it from cache.
|
|
466
|
+
Access updates the LRU ordering.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
key: The key to look up in the cache.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
The cached value if the key exists and has not expired, otherwise None.
|
|
473
|
+
"""
|
|
474
|
+
with self._lock:
|
|
475
|
+
node = self._node_map.get(key)
|
|
476
|
+
if node is None or node.is_expired():
|
|
477
|
+
# Key missing or expired: remove from map if still present
|
|
478
|
+
if node is not None:
|
|
479
|
+
del self._node_map[key]
|
|
480
|
+
return None
|
|
481
|
+
return node.value
|
|
482
|
+
|
|
483
|
+
def put(self, key: object, value: str, ttl_seconds: float) -> None:
|
|
484
|
+
"""
|
|
485
|
+
Insert or update a key-value pair with an associated time-to-live (TTL).
|
|
486
|
+
|
|
487
|
+
If the cache is full and the key already exists, it updates the existing
|
|
488
|
+
entry. Otherwise, the oldest LRU entry is evicted. TTL must be positive;
|
|
489
|
+
keys without expiration can be set with a large TTL value or omitted.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
key: The key to insert or update.
|
|
493
|
+
value: The value to associate with the key.
|
|
494
|
+
ttl_seconds: Time in seconds before the entry expires. Must be > 0.
|
|
495
|
+
|
|
496
|
+
Raises:
|
|
497
|
+
ValueError: If ttl_seconds is not a positive number.
|
|
498
|
+
"""
|
|
499
|
+
if ttl_seconds <= 0:
|
|
500
|
+
raise ValueError("ttl_seconds must be positive")
|
|
501
|
+
|
|
502
|
+
with self._lock:
|
|
503
|
+
current_time = time.time()
|
|
504
|
+
|
|
505
|
+
if key in self._node_map:
|
|
506
|
+
node = self._node_map[key]
|
|
507
|
+
node.value = value
|
|
508
|
+
node.ttl_seconds = ttl_seconds
|
|
509
|
+
node.expire_at = current_time + ttl_seconds
|
|
510
|
+
# Move to front (most recently used)
|
|
511
|
+
self._remove_node(node)
|
|
512
|
+
self._add_to_head(node)
|
|
513
|
+
else:
|
|
514
|
+
# Evict oldest LRU if at capacity
|
|
515
|
+
if len(self._node_map) >= self
|
|
516
|
+
|
|
517
|
+
=== GROUP 4: glm-4.7-flash ===
|
|
518
|
+
|
|
519
|
+
==========================================
|
|
520
|
+
MODEL: alpha-orin/glm-4.7-flash:q8_0
|
|
521
|
+
==========================================
|
|
522
|
+
LOAD: 64.0s | TOKENS: 1500 | DURATION: 74.3s | SPEED: 20.2 tok/s
|
|
523
|
+
|
|
524
|
+
1. **Understand the Goal:** The user wants a production-quality, thread-safe LRU (Least Recently Used) cache in Python with a specific feature: Time-To-Live (TTL) expiration for individual keys.
|
|
525
|
+
|
|
526
|
+
2. **Key Requirements Breakdown:**
|
|
527
|
+
* **Data Structure:** Doubly linked list + HashMap. No `OrderedDict`, no `functools.lru_cache`.
|
|
528
|
+
* **Operations:** `get(key)`, `put(key, value, ttl_seconds)`, `delete(key)`.
|
|
529
|
+
* **Concurrency:** Thread-safe using `threading.Lock`.
|
|
530
|
+
* **Eviction Strategy:** Lazy eviction (keys expire when accessed), though for an LRU, expired keys sitting at the back (tail) are usually evicted when the list fills up or a new element is added. "Lazy eviction on access" usually means you check expiration *before* returning the value, or during `get`. Since the prompt specifies "LRU", the eviction logic usually happens in `put` (when size limit is reached). If there's no hard size limit mentioned, it's unbounded, but standard LRU implies capacity limits. The prompt says "LRU cache", which usually implies a capacity. I should probably implement a capacity limit (e.g., max size) to make the LRU part meaningful, otherwise, it's just a map. Let's assume a default max size (like 128 or 1024) or make it configurable.
|
|
531
|
+
* **TTL:** Expired keys are removed when `get` is called (lazy eviction) or when they are moved to the front? The prompt says "Expired keys are lazily evicted on access". This is slightly ambiguous. Does it mean "evicted from the structure immediately" or "treated as missing"?
|
|
532
|
+
* *Interpretation A:* On `get`, check TTL. If expired, return `None` (or raise) and remove from LRU. If valid, move to front and return.
|
|
533
|
+
* *Interpretation B:* On `get`, check TTL. If expired, move to back (tail). The next eviction (on `put`) removes the tail. This is efficient. The prompt says "on access". Let's stick to the most robust behavior: Check on `get`. If expired, return default and remove. If valid, move to front.
|
|
534
|
+
* **Code Quality:** Type hints, docstrings, working example.
|
|
535
|
+
|
|
536
|
+
3. **Architecture Design:**
|
|
537
|
+
|
|
538
|
+
* **Node Class:** Needs `key`, `value`, `expiry`, `prev`, `next`. Needs type hints.
|
|
539
|
+
* **Cache Class:**
|
|
540
|
+
* `self._head`: Dummy head of the LRU list.
|
|
541
|
+
* `self._tail`: Dummy tail of the LRU list.
|
|
542
|
+
* `self._cache`: Dictionary mapping `key` -> `Node`.
|
|
543
|
+
* `self._lock`: `threading.Lock`.
|
|
544
|
+
* `self._maxsize`: Capacity limit.
|
|
545
|
+
|
|
546
|
+
4. **Algorithm Details:**
|
|
547
|
+
|
|
548
|
+
* **`__init__(self, maxsize=128)`:** Initialize structures.
|
|
549
|
+
* **`_add_to_front(self, node)`:** Insert `node` after dummy head.
|
|
550
|
+
* **`_remove_node(self, node)`:** Unlink `node` from list.
|
|
551
|
+
* **`_move_to_front(self, node)`:** Remove, then add to front.
|
|
552
|
+
* **`_expire_if_necessary(self, node)`:** Check if `node.expiry` < current time. If so, remove and return `True`. Else return `False`. *Self-correction based on requirement "lazy eviction on access":* Usually, if it's expired on access, you return "not found". If I remove it immediately, it's not in the structure anymore.
|
|
553
|
+
* **`get(key)`:**
|
|
554
|
+
1. Acquire lock.
|
|
555
|
+
2. If key not in dict -> Release lock -> Return None.
|
|
556
|
+
3. Get node.
|
|
557
|
+
4. Check expiry. If expired -> remove node from dict and list -> Release lock -> Return None.
|
|
558
|
+
5. If valid -> move node to front -> Release lock -> Return value.
|
|
559
|
+
* **`put(key, value, ttl_seconds)`:**
|
|
560
|
+
1. Acquire lock.
|
|
561
|
+
2. Calculate expiry time (`time.time() + ttl_seconds`).
|
|
562
|
+
3. If key exists:
|
|
563
|
+
* Update value, expiry.
|
|
564
|
+
* Move to front.
|
|
565
|
+
4. If key does not exist:
|
|
566
|
+
* Create node.
|
|
567
|
+
* Add to front.
|
|
568
|
+
* Add to dict.
|
|
569
|
+
* If size > maxsize: Remove tail (and delete from dict).
|
|
570
|
+
5. Release lock.
|
|
571
|
+
* **`delete(key)`:**
|
|
572
|
+
1. Acquire lock.
|
|
573
|
+
2. If key in dict: Remove node from list and dict.
|
|
574
|
+
3. Release lock.
|
|
575
|
+
|
|
576
|
+
5. **Refining the Requirements:**
|
|
577
|
+
* "Expired keys are lazily evicted on access": This implies that if I call `get(key)` on an expired item, it should effectively disappear.
|
|
578
|
+
* What about `put`? `put` is writing. Does it expire existing keys? No.
|
|
579
|
+
* What if I update a key with a new TTL? The expiry resets.
|
|
580
|
+
|
|
581
|
+
6. **Implementation Steps (Mental Draft):**
|
|
582
|
+
|
|
583
|
+
```python
|
|
584
|
+
class Node:
|
|
585
|
+
__slots__ = ('key', 'value', 'expiry', 'prev', 'next')
|
|
586
|
+
def __init__(self, key, value, expiry):
|
|
587
|
+
...
|
|
588
|
+
|
|
589
|
+
class TTLCache:
|
|
590
|
+
def __init__(self, maxsize=128):
|
|
591
|
+
self.maxsize = maxsize
|
|
592
|
+
self.head = Node(None, None, None) # Sentinel
|
|
593
|
+
self.tail = Node(None, None, None) # Sentinel
|
|
594
|
+
self.head.next = self.tail
|
|
595
|
+
self.tail.prev = self.head
|
|
596
|
+
self.map = {}
|
|
597
|
+
self.lock = threading.Lock()
|
|
598
|
+
|
|
599
|
+
def get(self, key):
|
|
600
|
+
with self.lock:
|
|
601
|
+
if key not in self.map:
|
|
602
|
+
return None
|
|
603
|
+
node = self.map[key]
|
|
604
|
+
if time.time() > node.expiry:
|
|
605
|
+
self._remove(node)
|
|
606
|
+
del self.map[key]
|
|
607
|
+
return None
|
|
608
|
+
self._move_to_front(node)
|
|
609
|
+
return node.value
|
|
610
|
+
|
|
611
|
+
def put(self, key, value, ttl_seconds):
|
|
612
|
+
with self.lock:
|
|
613
|
+
expiry = time.time() + ttl_seconds
|
|
614
|
+
if key in self.map:
|
|
615
|
+
node = self.map[key]
|
|
616
|
+
node.value = value
|
|
617
|
+
node.expiry = expiry
|
|
618
|
+
self._move_to_front(node)
|
|
619
|
+
else:
|
|
620
|
+
node = Node(key, value, expiry)
|
|
621
|
+
self._add(node)
|
|
622
|
+
self.map[key] = node
|
|
623
|
+
if len(self.map) > self.maxsize:
|
|
624
|
+
old_node = self._pop_tail()
|
|
625
|
+
del self.map[old_node.key]
|
|
626
|
+
# If expired key is at tail, should it be removed during put?
|
|
627
|
+
# Standard LRU: if tail is expired, pop it.
|
|
628
|
+
if self.tail
|
|
629
|
+
|
|
630
|
+
==========================================
|
|
631
|
+
MODEL: mac-studio/glm-4.7-flash:latest
|
|
632
|
+
==========================================
|
|
633
|
+
LOAD: 6.3s | TOKENS: 1500 | DURATION: 25.5s | SPEED: 58.8 tok/s
|
|
634
|
+
|
|
635
|
+
1. **Understand the Goal:** The user wants a thread-safe LRU (Least Recently Used) Cache implementation in Python.
|
|
636
|
+
* **Core Mechanism:** Doubly linked list + Hashmap (Dictionary).
|
|
637
|
+
* **Constraint:** No `OrderedDict` or `functools.lru_cache`.
|
|
638
|
+
* **Features:**
|
|
639
|
+
* `get(key)` -> returns value or None (implied, standard practice, though the prompt says "Support get", usually return None or raise, I'll stick to returning None on miss/expired).
|
|
640
|
+
* `put(key, value, ttl_seconds)` -> inserts or updates. Adds TTL logic.
|
|
641
|
+
* `delete(key)` -> removes key.
|
|
642
|
+
* **Concurrency:** Thread-safe using `threading.Lock`.
|
|
643
|
+
* **Eviction:** Expired keys are evicted *lazily* (on access/operation).
|
|
644
|
+
* **Documentation:** Full type hints and docstrings.
|
|
645
|
+
* **Output:** Complete, production-quality code.
|
|
646
|
+
|
|
647
|
+
2. **Architecture Design:**
|
|
648
|
+
|
|
649
|
+
* **Nodes:** Need a `Node` class for the linked list. Fields: `key`, `value`, `expiry`, `prev`, `next`.
|
|
650
|
+
* **Cache Class:**
|
|
651
|
+
* `self.capacity`: Maximum size.
|
|
652
|
+
* `self.head` & `self.tail`: Dummy nodes to simplify boundary conditions.
|
|
653
|
+
* `self.cache`: Dictionary mapping `key` -> `Node`.
|
|
654
|
+
* `self.lock`: Mutex.
|
|
655
|
+
* **Methods:**
|
|
656
|
+
* `_remove_node(node)`: Removes a node from the linked list.
|
|
657
|
+
* `_add_to_head(node)`: Moves a node to the front (MRU).
|
|
658
|
+
* `_move_to_head(node)`: Moves existing node to front.
|
|
659
|
+
* `_remove_key(key)`: Removes node and entry from cache.
|
|
660
|
+
* `_is_expired(node)`: Checks if current time > expiry.
|
|
661
|
+
* `get(key)`: Lock -> Check cache -> Check expiry -> Update LRU -> Unlock. Return value or None.
|
|
662
|
+
* `put(key, value, ttl_seconds)`: Lock -> Check if exists -> Update expiry/move to front -> Else -> Create new node -> Check capacity -> Remove old if full -> Add new. Unlock.
|
|
663
|
+
* `delete(key)`: Lock -> Remove -> Unlock.
|
|
664
|
+
* `_cleanup_expired()`: Optional but good for maintenance, though prompt implies lazy eviction. I will stick to strict lazy eviction to satisfy the requirement "Expired keys are lazily evicted on access". Actually, strictly speaking, if I just `get` an expired key, I should return None and remove it. If I don't `get` it, it stays until space forces eviction. I will handle expiration checks inside `get`, `put`, and the eviction logic.
|
|
665
|
+
|
|
666
|
+
3. **Refining the Logic (TTL):**
|
|
667
|
+
|
|
668
|
+
* Storing expiry in the Node.
|
|
669
|
+
* In `put`: `expiry = time.time() + ttl_seconds`.
|
|
670
|
+
* In `get`:
|
|
671
|
+
1. Check if `key` in `cache`.
|
|
672
|
+
2. Get node.
|
|
673
|
+
3. Check if expired.
|
|
674
|
+
4. If expired, `_remove_key(key)`, return None.
|
|
675
|
+
5. If valid, `_move_to_head(node)`, return `value`.
|
|
676
|
+
* In capacity management (`put`):
|
|
677
|
+
1. If key exists, update expiry, move to head. Return.
|
|
678
|
+
2. Create node.
|
|
679
|
+
3. Add to head.
|
|
680
|
+
4. Add to cache.
|
|
681
|
+
5. If size > capacity, remove from tail (LRU).
|
|
682
|
+
|
|
683
|
+
4. **Handling Thread Safety:**
|
|
684
|
+
|
|
685
|
+
* `threading.Lock` is required.
|
|
686
|
+
* Every public method (get, put, delete) must acquire the lock, do its work, and release it.
|
|
687
|
+
* *Self-correction:* Does `_is_expired` need a lock? Yes, because it uses `time.time()`, which isn't strictly atomic with the check, but usually acceptable. Better to have the whole block atomic.
|
|
688
|
+
|
|
689
|
+
5. **Drafting the Code (Mental or Scratchpad):**
|
|
690
|
+
|
|
691
|
+
* *Imports:* `time`, `threading`, `typing`.
|
|
692
|
+
* *Node Class:* Needs type hints.
|
|
693
|
+
* *Cache Class:*
|
|
694
|
+
* `__init__(self, capacity: int)`: Initialize dummy head/tail.
|
|
695
|
+
* `get`: Return `Optional[T]`.
|
|
696
|
+
* `put`: Return `None`.
|
|
697
|
+
* `delete`: Return `None`.
|
|
698
|
+
|
|
699
|
+
6. **Edge Cases:**
|
|
700
|
+
* Capacity 0? Handle gracefully (empty cache).
|
|
701
|
+
* Key exists with different TTL on `put`? Update it.
|
|
702
|
+
* Key expires while sitting in the tail? Doesn't matter, if it becomes tail and capacity fills, it gets evicted anyway. But if it becomes tail and isn't evicted, it's effectively "dead" data. The requirement says "lazily evicted on access". So if I never touch the expired item, it stays (unless evicted). This is standard behavior for TTL caches unless a cleanup thread is running.
|
|
703
|
+
|
|
704
|
+
7. **Implementation Details:**
|
|
705
|
+
|
|
706
|
+
* *Linked List Management:*
|
|
707
|
+
* `remove_node`: Update `prev.next` and `next.prev`.
|
|
708
|
+
* `add_to_head`: `node.next = head.next`, `node.prev = head`, `head.next.prev = node`, `head.next = node`.
|
|
709
|
+
* `remove_tail`: `node = tail.prev`, `remove_node(node)`.
|
|
710
|
+
* *Time Handling:* Use `time.time()`.
|
|
711
|
+
* *Type Hints:* Use `TypeVar`, `Generic`.
|
|
712
|
+
|
|
713
|
+
8. **Refining Type Hints:**
|
|
714
|
+
* `class LRUCache(Generic[T])`
|
|
715
|
+
* Methods:
|
|
716
|
+
* `def get(self, key: K) -> Optional[T]:`
|
|
717
|
+
* `def put(self, key: K, value: T, ttl_seconds: float) -> None:`
|
|
718
|
+
* `def delete(self, key: K) -> bool:` (Returning bool implies success/failure or just existence).
|
|
719
|
+
|
|
720
|
+
9. **Writing the Code (Iterative refinement):**
|
|
721
|
+
|
|
722
|
+
* *Imports:* `time`, `threading`, `typing`.
|
|
723
|
+
* *Node:*
|
|
724
|
+
```python
|
|
725
|
+
class Node:
|
|
726
|
+
def __init__(self, key: Any, value: T, expiry: float):
|
|
727
|
+
self.key = key
|
|
728
|
+
self.value = value
|
|
729
|
+
self.expiry = expiry
|
|
730
|
+
self.prev = None
|
|
731
|
+
self.next = None
|
|
732
|
+
```
|
|
733
|
+
* *Cache:*
|
|
734
|
+
```python
|
|
735
|
+
class LRUCache(Generic[T]):
|
|
736
|
+
def __init__(self, capacity: int):
|
|
737
|
+
self.capacity = capacity
|
|
738
|
+
self.head = Node(None, None, 0) # Dummy
|
|
739
|
+
self.tail = Node(None, None, 0) # Dummy
|
|
740
|
+
self.head.next = self.tail
|
|
741
|
+
self.tail.prev = self.head
|
|
742
|
+
self.cache: Dict[K, Node] = {}
|
|
743
|
+
self.lock = threading.Lock
|
|
744
|
+
|
|
745
|
+
BENCHMARK COMPLETE: Mon Mar 23 10:32:24 EDT 2026
|
|
746
|
+
|
|
747
|
+
=== ROUND 2: deepseek-r1:14b head-to-head ===
|
|
748
|
+
|
|
749
|
+
==========================================
|
|
750
|
+
MODEL: alpha-orin/deepseek-r1:14b
|
|
751
|
+
==========================================
|
|
752
|
+
LOAD: 9.9s | TOKENS: 1500 | DURATION: 109.7s | SPEED: 13.7 tok/s
|
|
753
|
+
|
|
754
|
+
Here is a complete implementation of a thread-safe LRU cache with per-key TTL expiration:
|
|
755
|
+
|
|
756
|
+
```python
|
|
757
|
+
import time
|
|
758
|
+
from threading import Lock
|
|
759
|
+
|
|
760
|
+
class Node:
|
|
761
|
+
"""A node in the doubly linked list representing a key-value pair and its metadata."""
|
|
762
|
+
|
|
763
|
+
def __init__(self, key, value, ttl_seconds):
|
|
764
|
+
self.key = key
|
|
765
|
+
self.value = value
|
|
766
|
+
self.prev = None
|
|
767
|
+
self.next = None
|
|
768
|
+
self.expiration_timestamp = time.time() + ttl_seconds
|
|
769
|
+
|
|
770
|
+
class LRUCache:
|
|
771
|
+
"""Thread-safe LRU cache with per-key TTL expiration."""
|
|
772
|
+
|
|
773
|
+
def __init__(self, max_size: int = 100):
|
|
774
|
+
"""
|
|
775
|
+
Initialize the cache.
|
|
776
|
+
|
|
777
|
+
Args:
|
|
778
|
+
max_size (int): Maximum number of items the cache can hold. Defaults to 100.
|
|
779
|
+
"""
|
|
780
|
+
self.max_size = max_size
|
|
781
|
+
self.cache_dict = {}
|
|
782
|
+
self.head = None
|
|
783
|
+
self.tail = None
|
|
784
|
+
self.lock = Lock()
|
|
785
|
+
|
|
786
|
+
def __enter__(self):
|
|
787
|
+
"""Acquire the lock for thread-safe operations."""
|
|
788
|
+
self.lock.acquire()
|
|
789
|
+
return self
|
|
790
|
+
|
|
791
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
792
|
+
"""Release the lock after operations are complete."""
|
|
793
|
+
self.lock.release()
|
|
794
|
+
|
|
795
|
+
def get(self, key: object) -> object:
|
|
796
|
+
"""
|
|
797
|
+
Retrieve a value from the cache by key.
|
|
798
|
+
|
|
799
|
+
Args:
|
|
800
|
+
key (object): The key to look up in the cache.
|
|
801
|
+
|
|
802
|
+
Returns:
|
|
803
|
+
object: The cached value if found and not expired; None otherwise.
|
|
804
|
+
"""
|
|
805
|
+
with self.lock:
|
|
806
|
+
if key not in self.cache_dict:
|
|
807
|
+
return None
|
|
808
|
+
node = self.cache_dict[key]
|
|
809
|
+
if time.time() >= node.expiration_timestamp:
|
|
810
|
+
self._remove_node(node)
|
|
811
|
+
return None
|
|
812
|
+
self._move_to_front(node)
|
|
813
|
+
return node.value
|
|
814
|
+
|
|
815
|
+
def put(self, key: object, value: object, ttl_seconds: float):
|
|
816
|
+
"""
|
|
817
|
+
Insert a new key-value pair into the cache with a specified TTL.
|
|
818
|
+
|
|
819
|
+
Args:
|
|
820
|
+
key (object): The key to store in the cache.
|
|
821
|
+
value (object): The value to associate with the key.
|
|
822
|
+
ttl_seconds (float): Time-to-live for this key in seconds.
|
|
823
|
+
"""
|
|
824
|
+
with self.lock:
|
|
825
|
+
if key in self.cache_dict:
|
|
826
|
+
node = self.cache_dict[key]
|
|
827
|
+
node.value = value
|
|
828
|
+
node.expiration_timestamp = time.time() + ttl_seconds
|
|
829
|
+
self._move_to_front(node)
|
|
830
|
+
return
|
|
831
|
+
if len(self.cache_dict) >= self.max_size:
|
|
832
|
+
self._evict_lru_node()
|
|
833
|
+
new_node = Node(key, value, ttl_seconds)
|
|
834
|
+
self._add_node(new_node)
|
|
835
|
+
|
|
836
|
+
def delete(self, key: object):
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
==========================================
|
|
840
|
+
MODEL: mac-studio/deepseek-r1:14b
|
|
841
|
+
==========================================
|
|
842
|
+
LOAD: 1.0s | TOKENS: 1500 | DURATION: 39.5s | SPEED: 38.0 tok/s
|
|
843
|
+
|
|
844
|
+
To solve this problem, we need to implement a thread-safe LRU (Least Recently Used) cache with per-key TTL (Time-to-Live) expiration. The cache should support get, put, and delete operations, ensuring that expired keys are lazily evicted when accessed. The implementation must use a doubly linked list and a hashmap internally for efficient operations and must be thread-safe using locks.
|
|
845
|
+
|
|
846
|
+
### Approach
|
|
847
|
+
1. **Data Structures**: Use a doubly linked list to maintain the order of recently used keys and a hashmap to store the key-value pairs along with their expiration times.
|
|
848
|
+
2. **Thread Safety**: Implement a lock to ensure that all operations modifying the cache are thread-safe.
|
|
849
|
+
3. **Lazy Eviction**: Expire keys only when they are accessed, ensuring efficient cache usage.
|
|
850
|
+
4. **TTL Handling**: Each key has its own TTL, which is checked upon access to determine if the key should be expired and evicted.
|
|
851
|
+
|
|
852
|
+
### Solution Code
|
|
853
|
+
|
|
854
|
+
```python
|
|
855
|
+
import time
|
|
856
|
+
from typing import Optional, Any, Dict, Tuple
|
|
857
|
+
from threading import Lock
|
|
858
|
+
|
|
859
|
+
class Node:
|
|
860
|
+
"""A node in the doubly linked list representing a key-value pair."""
|
|
861
|
+
__slots__ = ['prev', 'next', 'key', 'value', 'expiration_time']
|
|
862
|
+
|
|
863
|
+
def __init__(self, key: str, value: Any, expiration_time: float):
|
|
864
|
+
self.key = key
|
|
865
|
+
self.value = value
|
|
866
|
+
self.expiration_time = expiration_time
|
|
867
|
+
self.prev = None
|
|
868
|
+
self.next = None
|
|
869
|
+
|
|
870
|
+
class LRUCache:
|
|
871
|
+
"""A thread-safe LRU cache with per-key TTL expiration."""
|
|
872
|
+
|
|
873
|
+
def __init__(self, max_size: int = 10):
|
|
874
|
+
"""Initialize the LRU cache with a specified maximum size."""
|
|
875
|
+
self.max_size = max_size
|
|
876
|
+
self.hashmap: Dict[str, Node] = {}
|
|
877
|
+
self.head: Optional[Node] = None
|
|
878
|
+
self.tail: Optional[Node] = None
|
|
879
|
+
self.lock = Lock()
|
|
880
|
+
|
|
881
|
+
def __getitem__(self, key: str) -> Optional[Any]:
|
|
882
|
+
"""Retrieve the value associated with the key if it exists and is not expired."""
|
|
883
|
+
with self.lock:
|
|
884
|
+
node = self.hashmap.get(key)
|
|
885
|
+
if node is None:
|
|
886
|
+
return None
|
|
887
|
+
if time.time() > node.expiration_time:
|
|
888
|
+
self._remove_node(node)
|
|
889
|
+
return None
|
|
890
|
+
self._move_to_end(node)
|
|
891
|
+
return node.value
|
|
892
|
+
|
|
893
|
+
def __
|
|
894
|
+
|
|
895
|
+
=== ROUND 2: lfm2:24b — checking availability ===
|
|
896
|
+
|
|
897
|
+
=== ROUND 2: lfm2:24b head-to-head ===
|
|
898
|
+
|
|
899
|
+
==========================================
|
|
900
|
+
MODEL: alpha-orin/lfm2:24b
|
|
901
|
+
==========================================
|
|
902
|
+
LOAD: 30.5s | TOKENS: 1500 | DURATION: 37.9s | SPEED: 39.6 tok/s
|
|
903
|
+
|
|
904
|
+
```python
|
|
905
|
+
"""
|
|
906
|
+
Thread-safe LRU Cache with per-key TTL expiration.
|
|
907
|
+
|
|
908
|
+
Implementation:
|
|
909
|
+
- Uses a doubly linked list (head/tail) + hashmap (dict) for O(1) access.
|
|
910
|
+
- Each entry stores: key, value, expiration timestamp, and pointers.
|
|
911
|
+
- Thread-safety guaranteed via threading.Lock.
|
|
912
|
+
- Expired entries are removed lazily on get/put when accessed.
|
|
913
|
+
- Provides get, put, delete, and internal helper methods.
|
|
914
|
+
"""
|
|
915
|
+
|
|
916
|
+
import time
|
|
917
|
+
import threading
|
|
918
|
+
from typing import Any, Dict, Optional
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
class _Node:
|
|
922
|
+
"""
|
|
923
|
+
Internal doubly linked list node for LRU ordering.
|
|
924
|
+
"""
|
|
925
|
+
__slots__ = ("key", "value", "expire_at", "prev", "next")
|
|
926
|
+
|
|
927
|
+
def __init__(self, key: Any, value: Any, expire_at: Optional[float]):
|
|
928
|
+
self.key = key
|
|
929
|
+
self.value = value
|
|
930
|
+
self.expire_at = expire_at # None means no expiration
|
|
931
|
+
self.prev: Optional["_Node"] = None
|
|
932
|
+
self.next: Optional["_Node"] = None
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
class LRUCache:
|
|
936
|
+
"""
|
|
937
|
+
Thread-safe Least Recently Used (LRU) cache with per-key TTL expiration.
|
|
938
|
+
|
|
939
|
+
Features:
|
|
940
|
+
• O(1) get, put, delete (amortized)
|
|
941
|
+
• Lazy eviction of expired keys on access
|
|
942
|
+
• Explicit thread safety via threading.Lock
|
|
943
|
+
• Full type hints and docstrings
|
|
944
|
+
"""
|
|
945
|
+
|
|
946
|
+
def __init__(self, capacity: int = 128):
|
|
947
|
+
if capacity <= 0:
|
|
948
|
+
raise ValueError("Capacity must be a positive integer.")
|
|
949
|
+
self.capacity: int = capacity
|
|
950
|
+
self._map: Dict[Any, _Node] = {}
|
|
951
|
+
self._head: Optional[_Node] = None # Most recently used
|
|
952
|
+
self._tail: Optional[_Node] = None # Least recently used
|
|
953
|
+
self._lock: threading.Lock = threading.Lock()
|
|
954
|
+
|
|
955
|
+
# ------------------------------------------------------------------ #
|
|
956
|
+
# Internal helpers
|
|
957
|
+
# ------------------------------------------------------------------ #
|
|
958
|
+
def _remove_node(self, node: _Node) -> None:
|
|
959
|
+
"""Detach node from the linked list."""
|
|
960
|
+
if node.prev:
|
|
961
|
+
node.prev.next = node.next
|
|
962
|
+
else:
|
|
963
|
+
# Node is head
|
|
964
|
+
self._head = node.next
|
|
965
|
+
if node.next:
|
|
966
|
+
node.next.prev = node.prev
|
|
967
|
+
else:
|
|
968
|
+
# Node is tail
|
|
969
|
+
self._tail = node.prev
|
|
970
|
+
|
|
971
|
+
def _add_to_head(self, node: _Node) -> None:
|
|
972
|
+
"""Insert node right after head (most recent position)."""
|
|
973
|
+
node.prev = None
|
|
974
|
+
node.next = self._head
|
|
975
|
+
if self._head:
|
|
976
|
+
self._head.prev = node
|
|
977
|
+
self._head = node
|
|
978
|
+
if self._tail is None:
|
|
979
|
+
self._tail = node
|
|
980
|
+
|
|
981
|
+
def _move_to_head(self, node: _Node) -> None:
|
|
982
|
+
"""Move existing node to head (recently used)."""
|
|
983
|
+
if self._head is node:
|
|
984
|
+
return
|
|
985
|
+
self._remove_node(node)
|
|
986
|
+
self._add_to_head(node)
|
|
987
|
+
|
|
988
|
+
def _evict_expired(self) -> None:
|
|
989
|
+
"""
|
|
990
|
+
Lazy eviction of expired keys.
|
|
991
|
+
Called before any mutation (get/put) or on explicit request.
|
|
992
|
+
"""
|
|
993
|
+
now = time.time()
|
|
994
|
+
node = self._tail
|
|
995
|
+
while node and node.expire_at is not None and node.expire_at <= now:
|
|
996
|
+
# Remove tail (LRU)
|
|
997
|
+
self._remove_node(node)
|
|
998
|
+
del self._map[node.key]
|
|
999
|
+
node = self._tail
|
|
1000
|
+
|
|
1001
|
+
# ------------------------------------------------------------------ #
|
|
1002
|
+
# Public API
|
|
1003
|
+
# ------------------------------------------------------------------ #
|
|
1004
|
+
def get(self, key: Any) -> Optional[Any]:
|
|
1005
|
+
"""
|
|
1006
|
+
Retrieve value by key if present and not expired.
|
|
1007
|
+
|
|
1008
|
+
Returns:
|
|
1009
|
+
Value if found and not expired, else None.
|
|
1010
|
+
|
|
1011
|
+
Raises:
|
|
1012
|
+
KeyError if key not present.
|
|
1013
|
+
"""
|
|
1014
|
+
with self._lock:
|
|
1015
|
+
self._evict_expired()
|
|
1016
|
+
node = self._map.get(key)
|
|
1017
|
+
if node is None:
|
|
1018
|
+
raise KeyError(f"Key {key!r} not found.")
|
|
1019
|
+
if node.expire_at is not None and node.expire_at <= time.time():
|
|
1020
|
+
# Expired
|
|
1021
|
+
self._remove_node(node)
|
|
1022
|
+
del self._map[key]
|
|
1023
|
+
return None
|
|
1024
|
+
# Valid entry
|
|
1025
|
+
self._move_to_head(node)
|
|
1026
|
+
return node.value
|
|
1027
|
+
|
|
1028
|
+
def put(self, key: Any, value: Any, ttl_seconds: Optional[float] = None) -> None:
|
|
1029
|
+
"""
|
|
1030
|
+
Insert or update key with value. Optionally set TTL (seconds).
|
|
1031
|
+
|
|
1032
|
+
Args:
|
|
1033
|
+
key: Cache key.
|
|
1034
|
+
value: Value to store.
|
|
1035
|
+
ttl_seconds: Time-to-live in seconds. If None, no expiration.
|
|
1036
|
+
|
|
1037
|
+
Raises:
|
|
1038
|
+
ValueError if capacity is exceeded after eviction.
|
|
1039
|
+
"""
|
|
1040
|
+
with self._lock:
|
|
1041
|
+
self._evict_expired()
|
|
1042
|
+
expire_at = None
|
|
1043
|
+
if ttl_seconds is not None:
|
|
1044
|
+
expire_at = time.time() + ttl_seconds
|
|
1045
|
+
# If key exists, update
|
|
1046
|
+
if key in self._map:
|
|
1047
|
+
node = self._map[key]
|
|
1048
|
+
node.value = value
|
|
1049
|
+
node.expire_at = expire_at
|
|
1050
|
+
self._move_to_head(node)
|
|
1051
|
+
else:
|
|
1052
|
+
# New node
|
|
1053
|
+
node = _Node(key, value, expire_at)
|
|
1054
|
+
self._map[key] = node
|
|
1055
|
+
self._add_to_head(node)
|
|
1056
|
+
if len(self._map) > self.capacity:
|
|
1057
|
+
# Evict LRU
|
|
1058
|
+
self._evict_head()
|
|
1059
|
+
if len(self._map) > self.capacity:
|
|
1060
|
+
# Should not happen, but safety
|
|
1061
|
+
self._evict_head()
|
|
1062
|
+
# No exception raised; capacity respected
|
|
1063
|
+
|
|
1064
|
+
def delete(self, key: Any) -> None:
|
|
1065
|
+
"""
|
|
1066
|
+
Remove key from cache if present.
|
|
1067
|
+
|
|
1068
|
+
Raises:
|
|
1069
|
+
KeyError if key not present.
|
|
1070
|
+
"""
|
|
1071
|
+
with self._lock:
|
|
1072
|
+
self._evict_expired()
|
|
1073
|
+
node = self._map.get(key)
|
|
1074
|
+
if node is None:
|
|
1075
|
+
raise KeyError(f"Key {key!r} not found.")
|
|
1076
|
+
self._remove_node(node)
|
|
1077
|
+
del self._map[key]
|
|
1078
|
+
|
|
1079
|
+
def _evict_head(self) -> None:
|
|
1080
|
+
"""
|
|
1081
|
+
Remove LRU (tail) node.
|
|
1082
|
+
"""
|
|
1083
|
+
if self._tail is None:
|
|
1084
|
+
return
|
|
1085
|
+
node = self._tail
|
|
1086
|
+
self._remove_node(node)
|
|
1087
|
+
del self._map[node.key]
|
|
1088
|
+
|
|
1089
|
+
# ------------------------------------------------------------------ #
|
|
1090
|
+
# Convenience/testing helpers
|
|
1091
|
+
# ---------------------------------
|
|
1092
|
+
|
|
1093
|
+
==========================================
|
|
1094
|
+
MODEL: mac-studio/lfm2:24b
|
|
1095
|
+
==========================================
|
|
1096
|
+
LOAD: 2.2s | TOKENS: 1500 | DURATION: 14.9s | SPEED: 100.5 tok/s
|
|
1097
|
+
|
|
1098
|
+
```python
|
|
1099
|
+
"""
|
|
1100
|
+
Thread-safe LRU cache with per-key TTL expiration.
|
|
1101
|
+
|
|
1102
|
+
Implementation:
|
|
1103
|
+
- Uses a doubly linked list (head/tail) + hashmap (dict) for O(1) access.
|
|
1104
|
+
- Each entry stores: value, expiration timestamp, and pointers.
|
|
1105
|
+
- Thread-safe via threading.Lock.
|
|
1106
|
+
- Expired entries are removed lazily on get/put.
|
|
1107
|
+
- Type hints and docstrings provided for all methods.
|
|
1108
|
+
"""
|
|
1109
|
+
|
|
1110
|
+
import time
|
|
1111
|
+
import threading
|
|
1112
|
+
from typing import Any, Optional, Dict, Tuple
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
class _Node:
|
|
1116
|
+
"""
|
|
1117
|
+
Internal node for doubly linked list.
|
|
1118
|
+
Stores key, value, expiration, and pointers to next/prev nodes.
|
|
1119
|
+
"""
|
|
1120
|
+
__slots__ = ("key", "value", "expire_at", "prev", "next")
|
|
1121
|
+
|
|
1122
|
+
def __init__(self, key: Any, value: Any, expire_at: float):
|
|
1123
|
+
self.key = key
|
|
1124
|
+
self.value = value
|
|
1125
|
+
self.expire_at = expire_at
|
|
1126
|
+
self.prev: Optional[_Node] = None
|
|
1127
|
+
self.next: Optional[_Node] = None
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
class LRUCache:
|
|
1131
|
+
"""
|
|
1132
|
+
Thread-safe LRU cache with per-key TTL expiration.
|
|
1133
|
+
|
|
1134
|
+
Attributes:
|
|
1135
|
+
capacity (int): Maximum number of items.
|
|
1136
|
+
lock (threading.Lock): Synchronization primitive.
|
|
1137
|
+
head (Optional[_Node]): Most recently used end.
|
|
1138
|
+
tail (Optional[_Node]): Least recently used end.
|
|
1139
|
+
cache (Dict[Any, _Node]): Key -> node mapping.
|
|
1140
|
+
"""
|
|
1141
|
+
|
|
1142
|
+
def __init__(self, capacity: int = 128):
|
|
1143
|
+
if capacity <= 0:
|
|
1144
|
+
raise ValueError("Capacity must be positive.")
|
|
1145
|
+
self.capacity = capacity
|
|
1146
|
+
self.lock = threading.Lock()
|
|
1147
|
+
self.cache: Dict[Any, _Node] = {}
|
|
1148
|
+
self.head: Optional[_Node] = None
|
|
1149
|
+
self.tail: Optional[_Node] = None
|
|
1150
|
+
|
|
1151
|
+
# ------------------------------------------------------------------ #
|
|
1152
|
+
# Internal helpers
|
|
1153
|
+
# ------------------------------------------------------------------ #
|
|
1154
|
+
def _remove_node(self, node: _Node) -> None:
|
|
1155
|
+
"""Detach node from the linked list."""
|
|
1156
|
+
if node.prev:
|
|
1157
|
+
node.prev.next = node.next
|
|
1158
|
+
else:
|
|
1159
|
+
self.head = node.next # node was head
|
|
1160
|
+
if node.next:
|
|
1161
|
+
node.next.prev = node.prev
|
|
1162
|
+
else:
|
|
1163
|
+
self.tail = node.prev # node was tail
|
|
1164
|
+
|
|
1165
|
+
def _add_to_head(self, node: _Node) -> None:
|
|
1166
|
+
"""Insert node right after head."""
|
|
1167
|
+
node.next = self.head
|
|
1168
|
+
node.prev = None
|
|
1169
|
+
if self.head:
|
|
1170
|
+
self.head.prev = node
|
|
1171
|
+
self.head = node
|
|
1172
|
+
if self.tail is None:
|
|
1173
|
+
self.tail = node
|
|
1174
|
+
|
|
1175
|
+
def _move_to_head(self, node: _Node) -> None:
|
|
1176
|
+
"""Move existing node to head (recently used)."""
|
|
1177
|
+
if self.head is node:
|
|
1178
|
+
return
|
|
1179
|
+
self._remove_node(node)
|
|
1180
|
+
self._add_to_head(node)
|
|
1181
|
+
|
|
1182
|
+
def _evict_expired(self) -> None:
|
|
1183
|
+
"""
|
|
1184
|
+
Lazy eviction of expired keys.
|
|
1185
|
+
Removes the LRU entry if expired; otherwise, returns None.
|
|
1186
|
+
"""
|
|
1187
|
+
now = time.time()
|
|
1188
|
+
node = self.tail
|
|
1189
|
+
while node:
|
|
1190
|
+
if node.expire_at is not None and node.expire_at <= now:
|
|
1191
|
+
# Remove tail
|
|
1192
|
+
prev = node.prev
|
|
1193
|
+
if prev:
|
|
1194
|
+
prev.next = None
|
|
1195
|
+
else:
|
|
1196
|
+
self.head = None
|
|
1197
|
+
self.tail = prev
|
|
1198
|
+
self.cache.pop(node.key, None)
|
|
1199
|
+
node = node.prev
|
|
1200
|
+
# If nothing expired, return None
|
|
1201
|
+
return None
|
|
1202
|
+
|
|
1203
|
+
# ------------------------------------------------------------------ #
|
|
1204
|
+
# Public API
|
|
1205
|
+
# ------------------------------------------------------------------ #
|
|
1206
|
+
def get(self, key: Any) -> Optional[Any]:
|
|
1207
|
+
"""
|
|
1208
|
+
Retrieve value by key if present and not expired.
|
|
1209
|
+
|
|
1210
|
+
Returns:
|
|
1211
|
+
Value if found and not expired, otherwise None.
|
|
1212
|
+
|
|
1213
|
+
Raises:
|
|
1214
|
+
KeyError: If key not present.
|
|
1215
|
+
"""
|
|
1216
|
+
with self.lock:
|
|
1217
|
+
node = self.cache.get(key)
|
|
1218
|
+
if node is None:
|
|
1219
|
+
raise KeyError(f"Key {key!r} not found.")
|
|
1220
|
+
if node.expire_at is not None and node.expire_at <= time.time():
|
|
1221
|
+
# Expired: remove and raise KeyError
|
|
1222
|
+
self._remove_node(node)
|
|
1223
|
+
self.cache.pop(key, None)
|
|
1224
|
+
raise KeyError(f"Key {key!r} expired.")
|
|
1225
|
+
# Valid entry: move to head and return value
|
|
1226
|
+
self._move_to_head(node)
|
|
1227
|
+
return node.value
|
|
1228
|
+
|
|
1229
|
+
def put(self, key: Any, value: Any, ttl_seconds: Optional[float] = None) -> None:
|
|
1230
|
+
"""
|
|
1231
|
+
Insert or update key with value.
|
|
1232
|
+
|
|
1233
|
+
Args:
|
|
1234
|
+
key: Cache key.
|
|
1235
|
+
value: Value to store.
|
|
1236
|
+
ttl_seconds: Time-to-live in seconds. If None, no expiration.
|
|
1237
|
+
"""
|
|
1238
|
+
with self.lock:
|
|
1239
|
+
now = time.time()
|
|
1240
|
+
expire_at: Optional[float] = None
|
|
1241
|
+
if ttl_seconds is not None:
|
|
1242
|
+
expire_at = now + ttl_seconds
|
|
1243
|
+
# Remove expired node if present
|
|
1244
|
+
node = self.cache.get(key)
|
|
1245
|
+
if node:
|
|
1246
|
+
self._remove_node(node)
|
|
1247
|
+
self.cache.pop(key, None)
|
|
1248
|
+
|
|
1249
|
+
# Evict LRU if capacity exceeded
|
|
1250
|
+
if len(self.cache) >= self.capacity:
|
|
1251
|
+
self._evict_expired()
|
|
1252
|
+
if len(self.cache) >= self.capacity:
|
|
1253
|
+
# Remove least recently used
|
|
1254
|
+
if self.tail:
|
|
1255
|
+
del self.cache[self.tail.key]
|
|
1256
|
+
self.tail = self.tail.prev
|
|
1257
|
+
if self.tail:
|
|
1258
|
+
self.tail.next = None
|
|
1259
|
+
|
|
1260
|
+
# Create new node
|
|
1261
|
+
node = _Node(key, value, expire_at)
|
|
1262
|
+
self.cache[key] = node
|
|
1263
|
+
self._add_to_head(node)
|
|
1264
|
+
|
|
1265
|
+
def delete(self, key: Any) -> None:
|
|
1266
|
+
"""
|
|
1267
|
+
Remove key from cache if present.
|
|
1268
|
+
|
|
1269
|
+
Args:
|
|
1270
|
+
key: Key to delete.
|
|
1271
|
+
"""
|
|
1272
|
+
with self.lock:
|
|
1273
|
+
node = self.cache.pop(key, None)
|
|
1274
|
+
if node:
|
|
1275
|
+
self._remove_node(node)
|
|
1276
|
+
# Tail may become empty
|
|
1277
|
+
if self.tail is node:
|
|
1278
|
+
self.tail = node.prev
|
|
1279
|
+
if self.head is node:
|
|
1280
|
+
self.head = node.next
|
|
1281
|
+
|
|
1282
|
+
# ------------------------------------------------------------------ #
|
|
1283
|
+
# Example usage
|
|
1284
|
+
# ------------------------------------------------------------------ #
|
|
1285
|
+
@staticmethod
|
|
1286
|
+
def example():
|
|
1287
|
+
"""
|
|
1288
|
+
|
|
1289
|
+
ALL COMPLETE: Mon Mar 23 13:04:51 EDT 2026
|