@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,947 @@
|
|
|
1
|
+
NEW NODES CODING BENCHMARK — Mon Mar 23 06:57:19 EDT 2026
|
|
2
|
+
Task: Thread-safe LRU Cache with per-key TTL
|
|
3
|
+
Nodes: Mac Studio (100.115.74.106) + Mac Mini (100.107.132.71)
|
|
4
|
+
==========================================
|
|
5
|
+
|
|
6
|
+
==========================================
|
|
7
|
+
MODEL: mac-studio/llama3.2:3b
|
|
8
|
+
==========================================
|
|
9
|
+
LOAD: 7.8s | TOKENS: 860 | DURATION: 6.5s | SPEED: 132.9 tok/s
|
|
10
|
+
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
class Node:
|
|
15
|
+
"""Node in the doubly linked list."""
|
|
16
|
+
def __init__(self, key, value, ttl_seconds):
|
|
17
|
+
self.key = key
|
|
18
|
+
self.value = value
|
|
19
|
+
self.ttl_seconds = ttl_seconds
|
|
20
|
+
self.prev = None
|
|
21
|
+
self.next = None
|
|
22
|
+
|
|
23
|
+
class LRUCache:
|
|
24
|
+
"""Thread-safe LRU cache with per-key TTL expiration."""
|
|
25
|
+
def __init__(self, capacity: int):
|
|
26
|
+
"""Initialize the LRU cache with given capacity."""
|
|
27
|
+
self.capacity = capacity
|
|
28
|
+
self.size = 0
|
|
29
|
+
self.map = {}
|
|
30
|
+
self.lock = threading.Lock()
|
|
31
|
+
self.head = Node(0, None, 0)
|
|
32
|
+
self.tail = Node(0, None, 0)
|
|
33
|
+
self.head.next = self.tail
|
|
34
|
+
self.tail.prev = self.head
|
|
35
|
+
|
|
36
|
+
def _remove(self, node: Node):
|
|
37
|
+
"""Remove a node from the doubly linked list."""
|
|
38
|
+
prev_node = node.prev
|
|
39
|
+
next_node = node.next
|
|
40
|
+
prev_node.next = next_node
|
|
41
|
+
next_node.prev = prev_node
|
|
42
|
+
|
|
43
|
+
def _add(self, node: Node):
|
|
44
|
+
"""Add a node to the doubly linked list."""
|
|
45
|
+
prev_node = self.tail.prev
|
|
46
|
+
prev_node.next = node
|
|
47
|
+
node.prev = prev_node
|
|
48
|
+
node.next = self.tail
|
|
49
|
+
self.tail.prev = node
|
|
50
|
+
|
|
51
|
+
def _get(self, key: str) -> str:
|
|
52
|
+
"""Get the value for a given key."""
|
|
53
|
+
with self.lock:
|
|
54
|
+
if key not in self.map:
|
|
55
|
+
return None
|
|
56
|
+
node = self.map[key]
|
|
57
|
+
if node.ttl_seconds <= time.time():
|
|
58
|
+
self._remove(node)
|
|
59
|
+
if self.size == self.capacity:
|
|
60
|
+
lru_key = min(self.map, key=lambda k: self.map[k].ttl_seconds)
|
|
61
|
+
del self.map[lru_key]
|
|
62
|
+
self._remove(self.map[lru_key])
|
|
63
|
+
self._remove(node)
|
|
64
|
+
del self.map[key]
|
|
65
|
+
self.size -= 1
|
|
66
|
+
else:
|
|
67
|
+
node.ttl_seconds = time.time() + node.ttl_seconds
|
|
68
|
+
self._add(node)
|
|
69
|
+
self.size += 1
|
|
70
|
+
return node.value
|
|
71
|
+
|
|
72
|
+
def put(self, key: str, value: str, ttl_seconds: int):
|
|
73
|
+
"""Put a value for a given key with a TTL."""
|
|
74
|
+
with self.lock:
|
|
75
|
+
if key in self.map:
|
|
76
|
+
self._remove(self.map[key])
|
|
77
|
+
del self.map[key]
|
|
78
|
+
self.map[key] = Node(key, value, ttl_seconds)
|
|
79
|
+
self._add(self.map[key])
|
|
80
|
+
self.size += 1
|
|
81
|
+
if self.size > self.capacity:
|
|
82
|
+
lru_key = min(self.map, key=lambda k: self.map[k].ttl_seconds)
|
|
83
|
+
del self.map[lru_key]
|
|
84
|
+
self._remove(self.map[lru_key])
|
|
85
|
+
self.size -= 1
|
|
86
|
+
|
|
87
|
+
def delete(self, key: str):
|
|
88
|
+
"""Delete a key."""
|
|
89
|
+
with self.lock:
|
|
90
|
+
if key not in self.map:
|
|
91
|
+
return
|
|
92
|
+
self._remove(self.map[key])
|
|
93
|
+
del self.map[key]
|
|
94
|
+
self.size -= 1
|
|
95
|
+
|
|
96
|
+
def get(self, key: str) -> str:
|
|
97
|
+
"""Get the value for a given key."""
|
|
98
|
+
return self._get(key)
|
|
99
|
+
|
|
100
|
+
def put(self, key: str, value: str, ttl_seconds: int):
|
|
101
|
+
"""Put a value for a given key with a TTL."""
|
|
102
|
+
return self.put(key, value, ttl_seconds)
|
|
103
|
+
|
|
104
|
+
def delete(self, key: str):
|
|
105
|
+
"""Delete a key."""
|
|
106
|
+
return self.delete(key)
|
|
107
|
+
|
|
108
|
+
# Usage example
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
cache = LRUCache(2)
|
|
111
|
+
|
|
112
|
+
def put_and_get(cache: LRUCache, key: str, value: str, ttl_seconds: int):
|
|
113
|
+
cache.put(key, value, ttl_seconds)
|
|
114
|
+
time.sleep(1) # Simulate a delay
|
|
115
|
+
print(cache.get(key))
|
|
116
|
+
|
|
117
|
+
threads = []
|
|
118
|
+
for i in range(3):
|
|
119
|
+
thread = threading.Thread(target=put_and_get, args=(cache, f"key{i}", f"value{i}", 2))
|
|
120
|
+
thread.start()
|
|
121
|
+
threads.append(thread)
|
|
122
|
+
|
|
123
|
+
for thread in threads:
|
|
124
|
+
thread.join()
|
|
125
|
+
|
|
126
|
+
==========================================
|
|
127
|
+
MODEL: mac-studio/ministral-3:14b
|
|
128
|
+
==========================================
|
|
129
|
+
LOAD: 4.0s | TOKENS: 1500 | DURATION: 36.4s | SPEED: 41.2 tok/s
|
|
130
|
+
|
|
131
|
+
Here's a complete, production-quality implementation of a thread-safe LRU cache with per-key TTL expiration in Python:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
import time
|
|
135
|
+
from threading import Lock
|
|
136
|
+
from typing import Any, Dict, Generic, Optional, TypeVar
|
|
137
|
+
|
|
138
|
+
K = TypeVar('K')
|
|
139
|
+
V = TypeVar('V')
|
|
140
|
+
|
|
141
|
+
class Node(Generic[K, V]):
|
|
142
|
+
"""Doubly linked list node for the LRU cache implementation."""
|
|
143
|
+
def __init__(self, key: K, value: V, expiration: float):
|
|
144
|
+
self.key = key
|
|
145
|
+
self.value = value
|
|
146
|
+
self.expiration = expiration
|
|
147
|
+
self.prev: Optional['Node[K, V]'] = None
|
|
148
|
+
self.next: Optional['Node[K, V]'] = None
|
|
149
|
+
|
|
150
|
+
class LRUCacheWithTTL(Generic[K, V]):
|
|
151
|
+
"""Thread-safe LRU cache with per-key TTL expiration.
|
|
152
|
+
|
|
153
|
+
This implementation uses a doubly linked list for O(1) access to most recently used items
|
|
154
|
+
and a hashmap for O(1) lookups. Each key has its own TTL (time-to-live) expiration.
|
|
155
|
+
|
|
156
|
+
Attributes:
|
|
157
|
+
capacity: Maximum number of items the cache can hold
|
|
158
|
+
_cache: Dictionary mapping keys to nodes
|
|
159
|
+
_head: Most recently used node (dummy head)
|
|
160
|
+
_tail: Least recently used node (dummy tail)
|
|
161
|
+
_lock: Threading lock for thread safety
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, capacity: int = 128):
|
|
165
|
+
"""Initialize the cache with a given capacity.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
capacity: Maximum number of items the cache can hold
|
|
169
|
+
"""
|
|
170
|
+
if capacity <= 0:
|
|
171
|
+
raise ValueError("Capacity must be positive")
|
|
172
|
+
|
|
173
|
+
self.capacity = capacity
|
|
174
|
+
self._cache: Dict[K, Node[K, V]] = {}
|
|
175
|
+
self._head = Node(None, None, 0) # dummy head
|
|
176
|
+
self._tail = Node(None, None, 0) # dummy tail
|
|
177
|
+
self._head.next = self._tail
|
|
178
|
+
self._tail.prev = self._head
|
|
179
|
+
self._lock = Lock()
|
|
180
|
+
|
|
181
|
+
def _remove_node(self, node: Node[K, V]) -> None:
|
|
182
|
+
"""Remove a node from the linked list."""
|
|
183
|
+
prev_node = node.prev
|
|
184
|
+
next_node = node.next
|
|
185
|
+
prev_node.next = next_node
|
|
186
|
+
next_node.prev = prev_node
|
|
187
|
+
|
|
188
|
+
def _add_to_head(self, node: Node[K, V]) -> None:
|
|
189
|
+
"""Add a node right after the dummy head (most recently used)."""
|
|
190
|
+
node.next = self._head.next
|
|
191
|
+
node.prev = self._head
|
|
192
|
+
self._head.next.prev = node
|
|
193
|
+
self._head.next = node
|
|
194
|
+
|
|
195
|
+
def _move_to_head(self, node: Node[K, V]) -> None:
|
|
196
|
+
"""Move an existing node to the head (most recently used)."""
|
|
197
|
+
self._remove_node(node)
|
|
198
|
+
self._add_to_head(node)
|
|
199
|
+
|
|
200
|
+
def _evict_tail(self) -> None:
|
|
201
|
+
"""Remove and return the tail node (least recently used)."""
|
|
202
|
+
if self._tail.prev == self._head:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
tail_node = self._tail.prev
|
|
206
|
+
self._remove_node(tail_node)
|
|
207
|
+
del self._cache[tail_node.key]
|
|
208
|
+
return tail_node
|
|
209
|
+
|
|
210
|
+
def _is_expired(self, node: Node[K, V]) -> bool:
|
|
211
|
+
"""Check if a node has expired based on its TTL."""
|
|
212
|
+
return time.time() > node.expiration
|
|
213
|
+
|
|
214
|
+
def get(self, key: K) -> Optional[V]:
|
|
215
|
+
"""Get a value from the cache.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
key: The key to look up
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
The cached value if found and not expired, None otherwise
|
|
222
|
+
"""
|
|
223
|
+
with self._lock:
|
|
224
|
+
node = self._cache.get(key)
|
|
225
|
+
if node is None:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
if self._is_expired(node):
|
|
229
|
+
self._remove_node(node)
|
|
230
|
+
del self._cache[key]
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
self._move_to_head(node)
|
|
234
|
+
return node.value
|
|
235
|
+
|
|
236
|
+
def put(self, key: K, value: V, ttl_seconds: float) -> None:
|
|
237
|
+
"""Put a value into the cache with a TTL.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
key: The key to store
|
|
241
|
+
value: The value to cache
|
|
242
|
+
ttl_seconds: Time-to-live in seconds
|
|
243
|
+
"""
|
|
244
|
+
with self._lock:
|
|
245
|
+
# Check if key exists and is not expired
|
|
246
|
+
node = self._cache.get(key)
|
|
247
|
+
if node is not None:
|
|
248
|
+
if not self._is_expired(node):
|
|
249
|
+
self._remove_node(node)
|
|
250
|
+
else:
|
|
251
|
+
del self._cache[key]
|
|
252
|
+
|
|
253
|
+
# Create new node
|
|
254
|
+
expiration = time.time() + ttl_seconds
|
|
255
|
+
new_node = Node(key, value, expiration)
|
|
256
|
+
self._cache[key] = new_node
|
|
257
|
+
self._add_to_head(new_node)
|
|
258
|
+
|
|
259
|
+
# Evict if over capacity
|
|
260
|
+
if len(self._cache) > self.capacity:
|
|
261
|
+
self._evict_tail()
|
|
262
|
+
|
|
263
|
+
def delete(self, key: K) -> bool:
|
|
264
|
+
"""Delete a key from the cache.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
key: The key to remove
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
True if the key was found and removed, False otherwise
|
|
271
|
+
"""
|
|
272
|
+
with self._lock:
|
|
273
|
+
node = self._cache.get(key)
|
|
274
|
+
if node is None:
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
self._remove_node(node)
|
|
278
|
+
del self._cache[key]
|
|
279
|
+
return True
|
|
280
|
+
|
|
281
|
+
def __contains__(self, key: K) -> bool:
|
|
282
|
+
"""Check if a key exists in the cache and is not expired.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
key: The key to check
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
True if the key exists and is not expired, False otherwise
|
|
289
|
+
"""
|
|
290
|
+
with self._lock:
|
|
291
|
+
node = self._cache.get(key)
|
|
292
|
+
if node is None:
|
|
293
|
+
return False
|
|
294
|
+
return not self._is_expired(node)
|
|
295
|
+
|
|
296
|
+
def __len__(self) -> int:
|
|
297
|
+
"""Get the current number of items in the cache (non-expired)."""
|
|
298
|
+
with self._lock:
|
|
299
|
+
return sum(1 for node in self._cache.values() if not self._is_expired(node))
|
|
300
|
+
|
|
301
|
+
def clear(self) -> None:
|
|
302
|
+
"""Clear all items from the cache."""
|
|
303
|
+
with self._lock:
|
|
304
|
+
self._cache.clear()
|
|
305
|
+
self._head.next = self._tail
|
|
306
|
+
self._tail.prev = self._head
|
|
307
|
+
|
|
308
|
+
# Example usage
|
|
309
|
+
if __name__ == "__main__":
|
|
310
|
+
# Create a cache with capacity of 3
|
|
311
|
+
cache = LRUCacheWithTTL[int, str](capacity=3)
|
|
312
|
+
|
|
313
|
+
# Put some items with different TTLs
|
|
314
|
+
cache.put(1, "value1", ttl_seconds=5) # expires in 5 seconds
|
|
315
|
+
cache.put(2, "value2", ttl_seconds=10) # expires in 10 seconds
|
|
316
|
+
cache.put(3, "value3", ttl_seconds=2) # expires in 2 seconds
|
|
317
|
+
|
|
318
|
+
# Get items
|
|
319
|
+
print(cache.get(1)) # "value1"
|
|
320
|
+
print(cache.get(2))
|
|
321
|
+
|
|
322
|
+
==========================================
|
|
323
|
+
MODEL: mac-studio/gpt-oss:20b
|
|
324
|
+
==========================================
|
|
325
|
+
LOAD: 5.6s | TOKENS: 1500 | DURATION: 19.6s | SPEED: 76.4 tok/s
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
#!/usr/bin/env python3
|
|
329
|
+
"""
|
|
330
|
+
A production‑ready, thread‑safe LRU cache with per‑key TTL support.
|
|
331
|
+
|
|
332
|
+
Features
|
|
333
|
+
--------
|
|
334
|
+
* O(1) `get`, `put` and `delete` operations.
|
|
335
|
+
* Internally uses a doubly‑linked list plus a hash map – no
|
|
336
|
+
`OrderedDict` or other high‑level helpers.
|
|
337
|
+
* Thread‑safe: all public methods acquire a `threading.Lock`.
|
|
338
|
+
* Expired keys are removed lazily when accessed.
|
|
339
|
+
* Capacity (maximum number of entries) can be configured; when the
|
|
340
|
+
limit is reached the least‑recently used item is evicted.
|
|
341
|
+
* Full type hints and detailed docstrings.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
from __future__ import annotations
|
|
345
|
+
|
|
346
|
+
from dataclasses import dataclass
|
|
347
|
+
import time
|
|
348
|
+
import threading
|
|
349
|
+
from typing import Any, Dict, Optional
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@dataclass
|
|
353
|
+
class _Node:
|
|
354
|
+
"""
|
|
355
|
+
Doubly‑linked list node used internally by :class:`LRUCache`.
|
|
356
|
+
|
|
357
|
+
Attributes
|
|
358
|
+
----------
|
|
359
|
+
key : Any
|
|
360
|
+
The key stored in this node.
|
|
361
|
+
value : Any
|
|
362
|
+
The value stored in this node.
|
|
363
|
+
expire_at : float
|
|
364
|
+
Unix timestamp (seconds since epoch) when this entry expires.
|
|
365
|
+
prev : Optional[_Node]
|
|
366
|
+
Reference to the previous node in the list.
|
|
367
|
+
next : Optional[_Node]
|
|
368
|
+
Reference to the next node in the list.
|
|
369
|
+
"""
|
|
370
|
+
key: Any
|
|
371
|
+
value: Any
|
|
372
|
+
expire_at: float
|
|
373
|
+
prev: Optional["_Node"] = None
|
|
374
|
+
next: Optional["_Node"] = None
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class LRUCache:
|
|
378
|
+
"""
|
|
379
|
+
A least‑recently‑used (LRU) cache with per‑key time‑to‑live (TTL).
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
capacity : int, optional
|
|
384
|
+
Maximum number of items the cache can hold. When the limit is
|
|
385
|
+
exceeded the least‑recently used item is evicted. If omitted,
|
|
386
|
+
the cache defaults to 128 items.
|
|
387
|
+
|
|
388
|
+
Notes
|
|
389
|
+
-----
|
|
390
|
+
All public methods are thread‑safe. The cache stores expiration
|
|
391
|
+
timestamps in UTC seconds (`time.time()`), so TTL values should be
|
|
392
|
+
given in seconds.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
def __init__(self, capacity: int = 128) -> None:
|
|
396
|
+
if capacity <= 0:
|
|
397
|
+
raise ValueError("capacity must be a positive integer")
|
|
398
|
+
self.capacity: int = capacity
|
|
399
|
+
self.cache: Dict[Any, _Node] = {}
|
|
400
|
+
|
|
401
|
+
# Dummy head and tail nodes to simplify edge cases
|
|
402
|
+
self._head: _Node = _Node(key=None, value=None, expire_at=0.0)
|
|
403
|
+
self._tail: _Node = _Node(key=None, value=None, expire_at=0.0)
|
|
404
|
+
self._head.next = self._tail
|
|
405
|
+
self._tail.prev = self._head
|
|
406
|
+
|
|
407
|
+
self._lock = threading.Lock()
|
|
408
|
+
|
|
409
|
+
# ----------------------------------------------------------------------
|
|
410
|
+
# Private helper methods (all assume the lock is held)
|
|
411
|
+
# ----------------------------------------------------------------------
|
|
412
|
+
def _add_to_front(self, node: _Node) -> None:
|
|
413
|
+
"""Insert *node* immediately after the dummy head."""
|
|
414
|
+
node.next = self._head.next
|
|
415
|
+
node.prev = self._head
|
|
416
|
+
self._head.next.prev = node
|
|
417
|
+
self._head.next = node
|
|
418
|
+
|
|
419
|
+
def _remove(self, node: _Node) -> None:
|
|
420
|
+
"""Remove *node* from the linked list."""
|
|
421
|
+
prev_node = node.prev
|
|
422
|
+
next_node = node.next
|
|
423
|
+
if prev_node is not None:
|
|
424
|
+
prev_node.next = next_node
|
|
425
|
+
if next_node is not None:
|
|
426
|
+
next_node.prev = prev_node
|
|
427
|
+
node.prev = node.next = None
|
|
428
|
+
|
|
429
|
+
def _move_to_front(self, node: _Node) -> None:
|
|
430
|
+
"""Move an existing node to the front (most‑recently used)."""
|
|
431
|
+
self._remove(node)
|
|
432
|
+
self._add_to_front(node)
|
|
433
|
+
|
|
434
|
+
def _pop_tail(self) -> Optional[_Node]:
|
|
435
|
+
"""
|
|
436
|
+
Remove and return the least‑recently used node (the one just
|
|
437
|
+
|
|
438
|
+
==========================================
|
|
439
|
+
MODEL: mac-studio/devstral-small-2:24b
|
|
440
|
+
==========================================
|
|
441
|
+
LOAD: 5.2s | TOKENS: 1500 | DURATION: 60.5s | SPEED: 24.8 tok/s
|
|
442
|
+
|
|
443
|
+
Here's a thread-safe LRU cache implementation with per-key TTL expiration using a doubly linked list and hashmap:
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
import threading
|
|
447
|
+
import time
|
|
448
|
+
from typing import Any, Generic, TypeVar, Optional
|
|
449
|
+
|
|
450
|
+
K = TypeVar('K')
|
|
451
|
+
V = TypeVar('V')
|
|
452
|
+
|
|
453
|
+
class Node(Generic[K, V]):
|
|
454
|
+
"""Node in the doubly linked list."""
|
|
455
|
+
def __init__(self, key: K, value: V, expiration: float) -> None:
|
|
456
|
+
self.key = key
|
|
457
|
+
self.value = value
|
|
458
|
+
self.expiration = expiration
|
|
459
|
+
self.prev: Optional['Node[K, V]'] = None
|
|
460
|
+
self.next: Optional['Node[K, V]'] = None
|
|
461
|
+
|
|
462
|
+
class ThreadSafeLRUCache(Generic[K, V]):
|
|
463
|
+
"""Thread-safe LRU cache with per-key TTL expiration.
|
|
464
|
+
|
|
465
|
+
Uses a doubly linked list for O(1) access to most recent items and a hashmap for O(1) lookups.
|
|
466
|
+
Expired items are lazily evicted on access.
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
def __init__(self, capacity: int) -> None:
|
|
470
|
+
"""Initialize the cache with given capacity.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
capacity: Maximum number of items the cache can hold.
|
|
474
|
+
"""
|
|
475
|
+
if capacity <= 0:
|
|
476
|
+
raise ValueError("Capacity must be positive")
|
|
477
|
+
|
|
478
|
+
self.capacity = capacity
|
|
479
|
+
self.cache: dict[K, Node[K, V]] = {}
|
|
480
|
+
self.head: Optional[Node[K, V]] = None
|
|
481
|
+
self.tail: Optional[Node[K, V]] = None
|
|
482
|
+
self.lock = threading.Lock()
|
|
483
|
+
|
|
484
|
+
def _add_to_head(self, node: Node[K, V]) -> None:
|
|
485
|
+
"""Add node to the head of the list (most recently used)."""
|
|
486
|
+
node.prev = None
|
|
487
|
+
node.next = self.head
|
|
488
|
+
|
|
489
|
+
if self.head is not None:
|
|
490
|
+
self.head.prev = node
|
|
491
|
+
|
|
492
|
+
self.head = node
|
|
493
|
+
|
|
494
|
+
if self.tail is None:
|
|
495
|
+
self.tail = node
|
|
496
|
+
|
|
497
|
+
def _remove_node(self, node: Node[K, V]) -> None:
|
|
498
|
+
"""Remove node from the linked list."""
|
|
499
|
+
if node.prev is not None:
|
|
500
|
+
node.prev.next = node.next
|
|
501
|
+
else:
|
|
502
|
+
self.head = node.next
|
|
503
|
+
|
|
504
|
+
if node.next is not None:
|
|
505
|
+
node.next.prev = node.prev
|
|
506
|
+
else:
|
|
507
|
+
self.tail = node.prev
|
|
508
|
+
|
|
509
|
+
def _move_to_head(self, node: Node[K, V]) -> None:
|
|
510
|
+
"""Move node to the head of the list (mark as recently used)."""
|
|
511
|
+
self._remove_node(node)
|
|
512
|
+
self._add_to_head(node)
|
|
513
|
+
|
|
514
|
+
def _pop_tail(self) -> Optional[Node[K, V]]:
|
|
515
|
+
"""Remove and return the tail node (least recently used)."""
|
|
516
|
+
if self.tail is None:
|
|
517
|
+
return None
|
|
518
|
+
|
|
519
|
+
node = self.tail
|
|
520
|
+
self._remove_node(node)
|
|
521
|
+
return node
|
|
522
|
+
|
|
523
|
+
def _is_expired(self, node: Node[K, V]) -> bool:
|
|
524
|
+
"""Check if node has expired based on its TTL."""
|
|
525
|
+
return time.time() > node.expiration
|
|
526
|
+
|
|
527
|
+
def get(self, key: K) -> Optional[V]:
|
|
528
|
+
"""Get value for key if it exists and hasn't expired.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
key: Key to look up in cache.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
Value if found and not expired, None otherwise.
|
|
535
|
+
"""
|
|
536
|
+
with self.lock:
|
|
537
|
+
node = self.cache.get(key)
|
|
538
|
+
if node is None:
|
|
539
|
+
return None
|
|
540
|
+
|
|
541
|
+
if self._is_expired(node):
|
|
542
|
+
self._remove_node(node)
|
|
543
|
+
del self.cache[key]
|
|
544
|
+
return None
|
|
545
|
+
|
|
546
|
+
self._move_to_head(node)
|
|
547
|
+
return node.value
|
|
548
|
+
|
|
549
|
+
def put(self, key: K, value: V, ttl_seconds: float) -> None:
|
|
550
|
+
"""Insert or update key-value pair with TTL.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
key: Key to insert/update.
|
|
554
|
+
value: Value to store.
|
|
555
|
+
ttl_seconds: Time-to-live in seconds for this key.
|
|
556
|
+
"""
|
|
557
|
+
with self.lock:
|
|
558
|
+
expiration = time.time() + ttl_seconds
|
|
559
|
+
|
|
560
|
+
if key in self.cache:
|
|
561
|
+
node = self.cache[key]
|
|
562
|
+
node.value = value
|
|
563
|
+
node.expiration = expiration
|
|
564
|
+
self._move_to_head(node)
|
|
565
|
+
else:
|
|
566
|
+
if len(self.cache) >= self.capacity:
|
|
567
|
+
tail = self._pop_tail()
|
|
568
|
+
if tail is not None:
|
|
569
|
+
del self.cache[tail.key]
|
|
570
|
+
|
|
571
|
+
new_node = Node(key, value, expiration)
|
|
572
|
+
self.cache[key] = new_node
|
|
573
|
+
self._add_to_head(new_node)
|
|
574
|
+
|
|
575
|
+
def delete(self, key: K) -> bool:
|
|
576
|
+
"""Remove key from cache if it exists.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
key: Key to remove.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
True if key was found and removed, False otherwise.
|
|
583
|
+
"""
|
|
584
|
+
with self.lock:
|
|
585
|
+
node = self.cache.get(key)
|
|
586
|
+
if node is None:
|
|
587
|
+
return False
|
|
588
|
+
|
|
589
|
+
self._remove_node(node)
|
|
590
|
+
del self.cache[key]
|
|
591
|
+
return True
|
|
592
|
+
|
|
593
|
+
# Usage example
|
|
594
|
+
if __name__ == "__main__":
|
|
595
|
+
cache = ThreadSafeLRUCache[str, int](capacity=2)
|
|
596
|
+
|
|
597
|
+
# Test basic operations
|
|
598
|
+
cache.put("a", 1, ttl_seconds=1)
|
|
599
|
+
cache.put("b", 2, ttl_seconds=2)
|
|
600
|
+
|
|
601
|
+
print(cache.get("a")) # Output: 1
|
|
602
|
+
print(cache.get("b")) # Output: 2
|
|
603
|
+
|
|
604
|
+
# Test LRU eviction
|
|
605
|
+
cache.put("c", 3, ttl_seconds=3)
|
|
606
|
+
print(cache.get("a")) # Output: None (evicted)
|
|
607
|
+
print(cache.get("b")) # Output: 2
|
|
608
|
+
print(cache.get("c")) # Output: 3
|
|
609
|
+
|
|
610
|
+
# Test TTL expiration
|
|
611
|
+
time.sleep(1.5)
|
|
612
|
+
print(cache.get("b")) # Output: None (expired)
|
|
613
|
+
print(cache.get("c")) # Output: 3
|
|
614
|
+
|
|
615
|
+
# Test thread safety
|
|
616
|
+
def worker(key: str, value: int, ttl: float) -> None:
|
|
617
|
+
cache.put(key, value, ttl_seconds=ttl)
|
|
618
|
+
print(f"Put {key}: {cache.get(key)}")
|
|
619
|
+
|
|
620
|
+
threads = []
|
|
621
|
+
for i in range(5):
|
|
622
|
+
t = threading.Thread(target=worker, args=(f"key{i}", i, 5))
|
|
623
|
+
threads.append(t)
|
|
624
|
+
t.start()
|
|
625
|
+
|
|
626
|
+
for t in threads:
|
|
627
|
+
t.join()
|
|
628
|
+
|
|
629
|
+
# Test delete
|
|
630
|
+
print(cache.delete("key0")) # Output: True
|
|
631
|
+
print(cache.delete("nonexistent")) # Output: False
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
This implementation includes:
|
|
635
|
+
|
|
636
|
+
1. A doubly linked list (`Node` class) to track usage order
|
|
637
|
+
2. A hashmap (`cache` dict) for O(1) lookups
|
|
638
|
+
3. Thread safety using `threading.Lock`
|
|
639
|
+
4. Lazy expiration checking on `get()` operations
|
|
640
|
+
5. Full type hints and docstrings
|
|
641
|
+
6. A working usage example demonstrating:
|
|
642
|
+
- Basic put/get operations
|
|
643
|
+
- LRU eviction
|
|
644
|
+
- TTL expiration
|
|
645
|
+
|
|
646
|
+
==========================================
|
|
647
|
+
MODEL: mac-studio/qwen3-coder:30b
|
|
648
|
+
==========================================
|
|
649
|
+
LOAD: 7.3s | TOKENS: 1500 | DURATION: 17.3s | SPEED: 86.6 tok/s
|
|
650
|
+
|
|
651
|
+
```python
|
|
652
|
+
import threading
|
|
653
|
+
import time
|
|
654
|
+
from typing import Optional, Any, Dict
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
class LRUCacheNode:
|
|
658
|
+
"""Doubly linked list node for LRU cache."""
|
|
659
|
+
|
|
660
|
+
def __init__(self, key: str, value: Any, ttl_seconds: Optional[int] = None):
|
|
661
|
+
self.key = key
|
|
662
|
+
self.value = value
|
|
663
|
+
self.ttl_seconds = ttl_seconds
|
|
664
|
+
self.expiration_time = time.time() + ttl_seconds if ttl_seconds is not None else None
|
|
665
|
+
self.prev = None
|
|
666
|
+
self.next = None
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
class LRUCache:
|
|
670
|
+
"""
|
|
671
|
+
Thread-safe LRU cache with per-key TTL expiration.
|
|
672
|
+
|
|
673
|
+
This implementation uses a doubly linked list combined with a hash map to achieve
|
|
674
|
+
O(1) get and put operations. Keys are lazily evicted when accessed if expired.
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
def __init__(self, capacity: int = 128):
|
|
678
|
+
"""
|
|
679
|
+
Initialize the LRU cache with given capacity.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
capacity: Maximum number of items the cache can hold
|
|
683
|
+
"""
|
|
684
|
+
self.capacity = capacity
|
|
685
|
+
self.cache_map: Dict[str, LRUCacheNode] = {}
|
|
686
|
+
self.head = LRUCacheNode("", None) # Dummy head node
|
|
687
|
+
self.tail = LRUCacheNode("", None) # Dummy tail node
|
|
688
|
+
self.head.next = self.tail
|
|
689
|
+
self.tail.prev = self.head
|
|
690
|
+
self.lock = threading.Lock()
|
|
691
|
+
|
|
692
|
+
def _remove_node(self, node: LRUCacheNode) -> None:
|
|
693
|
+
"""
|
|
694
|
+
Remove a node from the doubly linked list.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
node: Node to remove
|
|
698
|
+
"""
|
|
699
|
+
node.prev.next = node.next
|
|
700
|
+
node.next.prev = node.prev
|
|
701
|
+
|
|
702
|
+
def _add_to_front(self, node: LRUCacheNode) -> None:
|
|
703
|
+
"""
|
|
704
|
+
Add a node to the front of the doubly linked list (most recently used).
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
node: Node to add
|
|
708
|
+
"""
|
|
709
|
+
node.next = self.head.next
|
|
710
|
+
node.prev = self.head
|
|
711
|
+
self.head.next.prev = node
|
|
712
|
+
self.head.next = node
|
|
713
|
+
|
|
714
|
+
def _move_to_front(self, node: LRUCacheNode) -> None:
|
|
715
|
+
"""
|
|
716
|
+
Move a node to the front of the list (mark as most recently used).
|
|
717
|
+
|
|
718
|
+
Args:
|
|
719
|
+
node: Node to move
|
|
720
|
+
"""
|
|
721
|
+
self._remove_node(node)
|
|
722
|
+
self._add_to_front(node)
|
|
723
|
+
|
|
724
|
+
def _is_expired(self, node: LRUCacheNode) -> bool:
|
|
725
|
+
"""
|
|
726
|
+
Check if a node is expired based on its TTL.
|
|
727
|
+
|
|
728
|
+
Args:
|
|
729
|
+
node: Node to check
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
True if the node is expired, False otherwise
|
|
733
|
+
"""
|
|
734
|
+
if node.expiration_time is None:
|
|
735
|
+
return False
|
|
736
|
+
return time.time() > node.expiration_time
|
|
737
|
+
|
|
738
|
+
def _evict_expired(self) -> None:
|
|
739
|
+
"""
|
|
740
|
+
Evict all expired nodes from the cache.
|
|
741
|
+
"""
|
|
742
|
+
current = self.tail.prev
|
|
743
|
+
while current != self.head and self._is_expired(current):
|
|
744
|
+
self._remove_node(current)
|
|
745
|
+
del self.cache_map[current.key]
|
|
746
|
+
current = self.tail.prev
|
|
747
|
+
|
|
748
|
+
def get(self, key: str) -> Optional[Any]:
|
|
749
|
+
"""
|
|
750
|
+
Get a value from the cache by key.
|
|
751
|
+
|
|
752
|
+
If the key is not found or has expired, returns None.
|
|
753
|
+
Also removes expired keys lazily during access.
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
key: Key to look up
|
|
757
|
+
|
|
758
|
+
Returns:
|
|
759
|
+
Value associated with key, or None if not found or expired
|
|
760
|
+
"""
|
|
761
|
+
with self.lock:
|
|
762
|
+
if key not in self.cache_map:
|
|
763
|
+
return None
|
|
764
|
+
|
|
765
|
+
node = self.cache_map[key]
|
|
766
|
+
|
|
767
|
+
# Check for expiration before returning
|
|
768
|
+
if self._is_expired(node):
|
|
769
|
+
self._remove_node(node)
|
|
770
|
+
del self.cache_map[key]
|
|
771
|
+
return None
|
|
772
|
+
|
|
773
|
+
# Move to front (most recently used)
|
|
774
|
+
self._move_to_front(node)
|
|
775
|
+
return node.value
|
|
776
|
+
|
|
777
|
+
def put(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
|
|
778
|
+
"""
|
|
779
|
+
Put a key-value pair in the cache with optional TTL.
|
|
780
|
+
|
|
781
|
+
If key already exists, updates its value and TTL.
|
|
782
|
+
Evicts least recently used items if necessary.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
key: Key to store
|
|
786
|
+
value: Value to store
|
|
787
|
+
ttl_seconds: Time-to-live in seconds (None means no expiration)
|
|
788
|
+
"""
|
|
789
|
+
with self.lock:
|
|
790
|
+
if key in self.cache_map:
|
|
791
|
+
# Update existing node
|
|
792
|
+
node = self.cache_map[key]
|
|
793
|
+
node.value = value
|
|
794
|
+
node.ttl_seconds = ttl_seconds
|
|
795
|
+
node.expiration_time = time.time() + ttl_seconds if ttl_seconds is not None else None
|
|
796
|
+
self._move_to_front(node)
|
|
797
|
+
else:
|
|
798
|
+
# Create new node
|
|
799
|
+
new_node = LRUCacheNode(key, value, ttl_seconds)
|
|
800
|
+
|
|
801
|
+
# Evict expired nodes before adding new one
|
|
802
|
+
self._evict_expired()
|
|
803
|
+
|
|
804
|
+
# Check if we need to evict oldest items
|
|
805
|
+
if len(self.cache_map) >= self.capacity:
|
|
806
|
+
# Remove least recently used (tail.prev)
|
|
807
|
+
lru_node = self.tail.prev
|
|
808
|
+
self._remove_node(lru_node)
|
|
809
|
+
del self.cache_map[lru_node.key]
|
|
810
|
+
|
|
811
|
+
# Add new node to front
|
|
812
|
+
self._add_to_front(new_node)
|
|
813
|
+
self.cache_map[key] = new_node
|
|
814
|
+
|
|
815
|
+
def delete(self, key: str) -> bool:
|
|
816
|
+
"""
|
|
817
|
+
Delete a key from the cache.
|
|
818
|
+
|
|
819
|
+
Args:
|
|
820
|
+
key: Key to delete
|
|
821
|
+
|
|
822
|
+
Returns:
|
|
823
|
+
True if key was found and deleted, False otherwise
|
|
824
|
+
"""
|
|
825
|
+
with self.lock:
|
|
826
|
+
if key not in self.cache_map:
|
|
827
|
+
return False
|
|
828
|
+
|
|
829
|
+
node = self.cache_map[key]
|
|
830
|
+
self._remove_node(node)
|
|
831
|
+
del self.cache_map[key]
|
|
832
|
+
return True
|
|
833
|
+
|
|
834
|
+
def size(self) -> int:
|
|
835
|
+
"""
|
|
836
|
+
Get the current number of items in the cache.
|
|
837
|
+
|
|
838
|
+
Returns:
|
|
839
|
+
Number of items in the cache
|
|
840
|
+
"""
|
|
841
|
+
with self.lock:
|
|
842
|
+
return len(self.cache_map)
|
|
843
|
+
|
|
844
|
+
def clear(self) -> None:
|
|
845
|
+
"""
|
|
846
|
+
Clear all items from the cache.
|
|
847
|
+
"""
|
|
848
|
+
with self.lock:
|
|
849
|
+
self.cache_map.clear()
|
|
850
|
+
self.head.next = self.tail
|
|
851
|
+
self.tail.prev = self.head
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
# Example usage
|
|
855
|
+
if __name__ == "__main__":
|
|
856
|
+
# Create a cache with capacity of 3
|
|
857
|
+
cache = LRUCache(capacity=3)
|
|
858
|
+
|
|
859
|
+
# Put some values
|
|
860
|
+
cache.put("a", 1, ttl_seconds=2) # Expires in 2 seconds
|
|
861
|
+
cache.put("b", 2, ttl_seconds=5) # Expires in 5 seconds
|
|
862
|
+
cache.put("c", 3) # No expiration
|
|
863
|
+
|
|
864
|
+
print(f"Cache size: {cache.size()}") # Should be 3
|
|
865
|
+
|
|
866
|
+
# Get values
|
|
867
|
+
print(cache.get("a")) # Should return 1
|
|
868
|
+
print(cache.get("b")) # Should return 2
|
|
869
|
+
|
|
870
|
+
# Wait for key "a" to expire
|
|
871
|
+
time.sleep(2.5)
|
|
872
|
+
|
|
873
|
+
# Try accessing expired key
|
|
874
|
+
print(cache.get("a")) # Should return None
|
|
875
|
+
|
|
876
|
+
==========================================
|
|
877
|
+
MODEL: mac-mini/qwen3.5:27b
|
|
878
|
+
==========================================
|
|
879
|
+
LOAD: 11.8s | TOKENS: 1500 | DURATION: 406.5s | SPEED: 3.7 tok/s
|
|
880
|
+
|
|
881
|
+
Here's a thinking process that leads to the suggested implementation:
|
|
882
|
+
|
|
883
|
+
1. **Analyze the Request:**
|
|
884
|
+
* **Goal:** Implement a thread-safe LRU (Least Recently Used) cache in Python.
|
|
885
|
+
* **Specific Feature:** Per-key TTL (Time-To-Live).
|
|
886
|
+
* **Internal Structure:** Doubly Linked List + Hash Map (Dictionary). Explicitly forbids `OrderedDict` and `functools.lru_cache`.
|
|
887
|
+
* **Methods:** `get(key)`, `put(key, value, ttl_seconds)`, `delete(key)`.
|
|
888
|
+
* **Concurrency:** Thread-safe using `threading.Lock`.
|
|
889
|
+
* **Expiration Logic:** Lazy eviction (keys expire only when accessed if TTL passed). Note: This usually implies checking expiry on `get` or `put`.
|
|
890
|
+
* **Quality:** Production-quality, full type hints, docstrings.
|
|
891
|
+
* **Output:** Working usage example included.
|
|
892
|
+
|
|
893
|
+
2. **Design the Data Structures:**
|
|
894
|
+
* **Node Class:** Needs to hold `key`, `value`, `expiry_time` (absolute timestamp), and pointers `prev`/`next`.
|
|
895
|
+
* **HashMap:** A standard `dict[str, Node]` mapping keys to node objects for O(1) access.
|
|
896
|
+
* **Doubly Linked List:** Needs head and tail sentinel nodes (dummy nodes) to simplify insertion/removal at ends.
|
|
897
|
+
* Head: Most Recently Used (MRU).
|
|
898
|
+
* Tail: Least Recently Used (LRU).
|
|
899
|
+
* **Lock:** A `threading.RLock` or `threading.Lock`. Since we might be calling internal methods that hold locks, an `RLock` is often safer to prevent deadlocks if a method re-enters the lock context, though for simple cache ops `Lock` usually suffices. Let's stick to `threading.Lock` as per strict requirement, but `RLock` handles recursion better. The prompt says `threading.Lock`, so I will use `threading.Lock`. Wait, actually, if I have a method that calls another synchronized method holding the lock, I need `RLock`. However, standard cache implementations usually wrap the critical section in one block. I will use `threading.Lock` but structure code carefully to avoid recursion. Actually, `RLock` is safer for re-entrancy (e.g., if `delete` calls internal cleanup that might try to acquire). Let's stick to the prompt: "Thread-safe using threading.Lock". I'll use `threading.Lock` but be mindful of critical sections.
|
|
900
|
+
|
|
901
|
+
3. **Design the Logic:**
|
|
902
|
+
* **Capacity:** The prompt didn't specify a max size limit, just LRU behavior + TTL. However, a cache usually implies eviction based on size. Without a max size, it's technically an "LRU dictionary with expiration". But to make it truly an LRU *Cache*, I need a `capacity` parameter in `__init__`. If I don't implement capacity, the Linked List logic for LRU is less meaningful (we only move nodes to front on access). Let's add `capacity` but default it to infinite or None. To strictly satisfy "LRU Cache", eviction based on size is crucial. I will include `max_size` in `__init__`.
|
|
903
|
+
* **`put(key, value, ttl)`:**
|
|
904
|
+
1. Acquire lock.
|
|
905
|
+
2. Check if key exists.
|
|
906
|
+
* If yes: Update node (value, expiry, move to MRU).
|
|
907
|
+
* If no: Create new node. Insert at MRU. Increment size.
|
|
908
|
+
3. If capacity exceeded and new node added: Evict LRU node (tail.next). Check map deletion.
|
|
909
|
+
4. Release lock.
|
|
910
|
+
* **`get(key)`:**
|
|
911
|
+
1. Acquire lock.
|
|
912
|
+
2. Check if key exists in map.
|
|
913
|
+
3. If exists:
|
|
914
|
+
* Check TTL (time.time() > expiry_time).
|
|
915
|
+
* If expired: Remove node, remove from map, return None.
|
|
916
|
+
* If not expired: Move to MRU, return value.
|
|
917
|
+
4. Release lock.
|
|
918
|
+
* **`delete(key)`:**
|
|
919
|
+
1. Acquire lock.
|
|
920
|
+
2. Check if key exists.
|
|
921
|
+
3. Remove node from list and map.
|
|
922
|
+
4. Release lock.
|
|
923
|
+
|
|
924
|
+
4. **Refining Expiration Logic (Lazy vs Active):**
|
|
925
|
+
* Requirement: "Expired keys are lazily evicted on access".
|
|
926
|
+
* This means `get(key)` checks TTL. If expired, remove it.
|
|
927
|
+
* What about `put`? If we overwrite an existing key, we should update its TTL.
|
|
928
|
+
* What about background cleanup? The requirement specifically says "lazy eviction on access", implying no background threads for expiration. I will follow this strictly. However, relying solely on lazy eviction means expired entries stay in memory until accessed again, which might bloat memory. A production-quality cache usually has a mechanism to handle this, but adhering to the prompt's constraint ("lazily evicted"), I will not add a background cleaner thread. I will focus on the "access" part.
|
|
929
|
+
* Wait, if I don't check expiration in `put` (when overwriting), an old key stays expired until `get` or next access? No, `put` updates it. The issue is keys that are never accessed again after expiring. They remain in the map/list until a size eviction happens (if capacity exists) or until explicitly deleted. This is acceptable for "lazy".
|
|
930
|
+
|
|
931
|
+
5. **Drafting the Code (Mental or Scratchpad):**
|
|
932
|
+
* `Node` class: `key`, `value`, `expire_at`, `prev`, `next`.
|
|
933
|
+
* `_DoublyLinkedList`: Head, Tail pointers. Methods `_add_to_head(node)`, `_remove_node(node)`.
|
|
934
|
+
* `LRUCache`: `__init__(self, capacity=...)`. `_cache` dict. `current_size`. `lock`.
|
|
935
|
+
* Imports: `time`, `threading`, `typing` (Optional).
|
|
936
|
+
|
|
937
|
+
6. **Addressing "Production Quality":**
|
|
938
|
+
* Type hints everywhere.
|
|
939
|
+
* Docstrings for class and methods.
|
|
940
|
+
* Handle `None` inputs gracefully? Key should be hashable. Value any object. TTL float/int >= 0.
|
|
941
|
+
* Context manager for lock (`with self.lock:`).
|
|
942
|
+
* Sentinel nodes (dummy head/tail) simplify edge cases (empty list).
|
|
943
|
+
|
|
944
|
+
7. **Potential Edge Cases:**
|
|
945
|
+
* `ttl_seconds` = 0? Should expire immediately or act like no TTL? Usually 0 means immediate expiry or effectively "cache for a moment". Standard logic: `time.time() + ttl`. If 0, it expires immediately on next check. Let
|
|
946
|
+
==========================================
|
|
947
|
+
BENCHMARK COMPLETE: Mon Mar 23 07:09:03 EDT 2026
|