@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.
Files changed (581) hide show
  1. package/README.md +5 -2
  2. package/bin/alice-cloud.cjs +71 -55
  3. package/package.json +1 -1
  4. package/templates/skills/acculynx/SKILL.md +183 -0
  5. package/templates/skills/acculynx/references/analysis_template.py +116 -0
  6. package/templates/skills/acculynx/references/dashboard_page.tsx +641 -0
  7. package/templates/skills/claude-code/SKILL.md +2 -2
  8. package/templates/skills/coding-agent/SKILL.md +68 -0
  9. package/templates/skills/crawl4ai/SKILL.md +119 -0
  10. package/templates/skills/crawl4ai/scripts/crwl +3 -0
  11. package/templates/workspaces/accuscope/AGENTS.md +38 -0
  12. package/templates/workspaces/accuscope/FEEDBACK.md +27 -0
  13. package/templates/workspaces/accuscope/HEARTBEAT.md +26 -0
  14. package/templates/workspaces/accuscope/IDENTITY.md +48 -0
  15. package/templates/workspaces/accuscope/LEARNINGS.md +46 -0
  16. package/templates/workspaces/accuscope/MEMORY.md +47 -0
  17. package/templates/workspaces/accuscope/PLAYBOOK.md +65 -0
  18. package/templates/workspaces/accuscope/SOUL.md +40 -0
  19. package/templates/workspaces/accuscope/TOOLS.md +63 -0
  20. package/templates/workspaces/accuscope/USER.md +39 -0
  21. package/templates/workspaces/aiden/AGENTS.md +52 -0
  22. package/templates/workspaces/aiden/FEEDBACK.md +12 -0
  23. package/templates/workspaces/aiden/HEARTBEAT.md +9 -0
  24. package/templates/workspaces/aiden/IDENTITY.md +6 -0
  25. package/templates/workspaces/aiden/LEARNINGS.md +6 -0
  26. package/templates/workspaces/aiden/MEMORY.md +22 -0
  27. package/templates/workspaces/aiden/PLAYBOOK.md +16 -0
  28. package/templates/workspaces/aiden/SOUL.md +1 -1
  29. package/templates/workspaces/aiden/USER.md +17 -0
  30. package/templates/workspaces/alex/AGENTS.md +52 -0
  31. package/templates/workspaces/alex/FEEDBACK.md +11 -0
  32. package/templates/workspaces/alex/HEARTBEAT.md +9 -0
  33. package/templates/workspaces/alex/IDENTITY.md +6 -0
  34. package/templates/workspaces/alex/LEARNINGS.md +5 -0
  35. package/templates/workspaces/alex/MEMORY.md +22 -0
  36. package/templates/workspaces/alex/PLAYBOOK.md +16 -0
  37. package/templates/workspaces/alex/SOUL.md +1 -1
  38. package/templates/workspaces/alex/USER.md +13 -0
  39. package/templates/workspaces/aria/AGENTS.md +18 -0
  40. package/templates/workspaces/aria/FEEDBACK.md +12 -0
  41. package/templates/workspaces/aria/HEARTBEAT.md +32 -0
  42. package/templates/workspaces/aria/IDENTITY.md +12 -0
  43. package/templates/workspaces/aria/LEARNINGS.md +31 -0
  44. package/templates/workspaces/aria/MEMORY.md +29 -0
  45. package/templates/workspaces/aria/PLAYBOOK.md +71 -0
  46. package/templates/workspaces/aria/SOUL.md +57 -0
  47. package/templates/workspaces/aria/TOOLS.md +47 -0
  48. package/templates/workspaces/aria/USER.md +18 -0
  49. package/templates/workspaces/audrey/AGENTS.md +59 -0
  50. package/templates/workspaces/audrey/FEEDBACK.md +11 -0
  51. package/templates/workspaces/audrey/HEARTBEAT.md +9 -0
  52. package/templates/workspaces/audrey/IDENTITY.md +6 -0
  53. package/templates/workspaces/audrey/LEARNINGS.md +5 -0
  54. package/templates/workspaces/audrey/MEMORY.md +22 -0
  55. package/templates/workspaces/audrey/PLAYBOOK.md +16 -0
  56. package/templates/workspaces/audrey/SOUL.md +1 -1
  57. package/templates/workspaces/audrey/TOOLS.md +15 -0
  58. package/templates/workspaces/audrey/USER.md +13 -0
  59. package/templates/workspaces/avery/AGENTS.md +52 -0
  60. package/templates/workspaces/avery/FEEDBACK.md +12 -0
  61. package/templates/workspaces/avery/HEARTBEAT.md +5 -0
  62. package/templates/workspaces/avery/IDENTITY.md +6 -0
  63. package/templates/workspaces/avery/LEARNINGS.md +6 -0
  64. package/templates/workspaces/avery/MEMORY.md +22 -0
  65. package/templates/workspaces/avery/PLAYBOOK.md +16 -0
  66. package/templates/workspaces/avery/SOUL.md +1 -1
  67. package/templates/workspaces/avery/USER.md +17 -0
  68. package/templates/workspaces/avery/skills/claude-code/SKILL.md +38 -0
  69. package/templates/workspaces/avery/skills/claude-code/claude_code +55 -0
  70. package/templates/workspaces/caleb/AGENTS.md +52 -0
  71. package/templates/workspaces/caleb/FEEDBACK.md +11 -0
  72. package/templates/workspaces/caleb/HEARTBEAT.md +9 -0
  73. package/templates/workspaces/caleb/IDENTITY.md +6 -0
  74. package/templates/workspaces/caleb/LEARNINGS.md +5 -0
  75. package/templates/workspaces/caleb/MEMORY.md +22 -0
  76. package/templates/workspaces/caleb/PLAYBOOK.md +16 -0
  77. package/templates/workspaces/caleb/SOUL.md +1 -1
  78. package/templates/workspaces/caleb/TOOLS.md +30 -0
  79. package/templates/workspaces/caleb/USER.md +13 -0
  80. package/templates/workspaces/clara/AGENTS.md +59 -0
  81. package/templates/workspaces/clara/FEEDBACK.md +12 -0
  82. package/templates/workspaces/clara/HEARTBEAT.md +5 -0
  83. package/templates/workspaces/clara/IDENTITY.md +6 -0
  84. package/templates/workspaces/clara/LEARNINGS.md +6 -0
  85. package/templates/workspaces/clara/MEMORY.md +22 -0
  86. package/templates/workspaces/clara/PLAYBOOK.md +16 -0
  87. package/templates/workspaces/clara/SOUL.md +1 -1
  88. package/templates/workspaces/clara/TOOLS.md +15 -0
  89. package/templates/workspaces/clara/USER.md +17 -0
  90. package/templates/workspaces/daphne/AGENTS.md +59 -0
  91. package/templates/workspaces/daphne/FEEDBACK.md +18 -0
  92. package/templates/workspaces/daphne/HEARTBEAT.md +5 -0
  93. package/templates/workspaces/daphne/IDENTITY.md +6 -0
  94. package/templates/workspaces/daphne/LEARNINGS.md +6 -0
  95. package/templates/workspaces/daphne/MEMORY.md +22 -0
  96. package/templates/workspaces/daphne/PLAYBOOK.md +48 -0
  97. package/templates/workspaces/daphne/SOUL.md +1 -1
  98. package/templates/workspaces/daphne/TOOLS.md +15 -0
  99. package/templates/workspaces/daphne/USER.md +17 -0
  100. package/templates/workspaces/darius/AGENTS.md +52 -0
  101. package/templates/workspaces/darius/FEEDBACK.md +12 -0
  102. package/templates/workspaces/darius/HEARTBEAT.md +5 -0
  103. package/templates/workspaces/darius/IDENTITY.md +6 -0
  104. package/templates/workspaces/darius/LEARNINGS.md +6 -0
  105. package/templates/workspaces/darius/MEMORY.md +22 -0
  106. package/templates/workspaces/darius/PLAYBOOK.md +16 -0
  107. package/templates/workspaces/darius/SOUL.md +1 -1
  108. package/templates/workspaces/darius/USER.md +17 -0
  109. package/templates/workspaces/darius/skills/claude-code/SKILL.md +38 -0
  110. package/templates/workspaces/darius/skills/claude-code/claude_code +55 -0
  111. package/templates/workspaces/devon/AGENTS.md +52 -0
  112. package/templates/workspaces/devon/FEEDBACK.md +11 -0
  113. package/templates/workspaces/devon/HEARTBEAT.md +5 -0
  114. package/templates/workspaces/devon/IDENTITY.md +6 -0
  115. package/templates/workspaces/devon/LEARNINGS.md +11 -0
  116. package/templates/workspaces/devon/MEMORY.md +22 -0
  117. package/templates/workspaces/devon/PLAYBOOK.md +16 -0
  118. package/templates/workspaces/devon/SOUL.md +1 -1
  119. package/templates/workspaces/devon/USER.md +13 -0
  120. package/templates/workspaces/devon/check_github.py +12 -0
  121. package/templates/workspaces/devon/check_mc_env.py +30 -0
  122. package/templates/workspaces/devon/check_sb.py +34 -0
  123. package/templates/workspaces/devon/check_vercel.py +12 -0
  124. package/templates/workspaces/devon/get_mc_files.py +17 -0
  125. package/templates/workspaces/devon/write_heartbeat.py +67 -0
  126. package/templates/workspaces/dylan/.env.example +33 -0
  127. package/templates/workspaces/dylan/00007_verify_licenses_table.sql +100 -0
  128. package/templates/workspaces/dylan/AGENTS.md +52 -0
  129. package/templates/workspaces/dylan/FEEDBACK.md +28 -0
  130. package/templates/workspaces/dylan/HEARTBEAT.md +5 -0
  131. package/templates/workspaces/dylan/IDENTITY.md +6 -0
  132. package/templates/workspaces/dylan/LEARNINGS.md +70 -0
  133. package/templates/workspaces/dylan/MEMORY.md +22 -0
  134. package/templates/workspaces/dylan/PLAYBOOK.md +16 -0
  135. package/templates/workspaces/dylan/SOUL.md +1 -1
  136. package/templates/workspaces/dylan/STRIPE_PIPELINE.md +185 -0
  137. package/templates/workspaces/dylan/USER.md +17 -0
  138. package/templates/workspaces/dylan/n8n-stripe-welcome-workflow.json +123 -0
  139. package/templates/workspaces/dylan/skills/claude-code/SKILL.md +38 -0
  140. package/templates/workspaces/dylan/skills/claude-code/claude_code +55 -0
  141. package/templates/workspaces/dylan/stripe-webhook-handler.py +433 -0
  142. package/templates/workspaces/dylan/test_mock_webhook.py +103 -0
  143. package/templates/workspaces/elena/AGENTS.md +59 -0
  144. package/templates/workspaces/elena/FEEDBACK.md +11 -0
  145. package/templates/workspaces/elena/HEARTBEAT.md +9 -0
  146. package/templates/workspaces/elena/IDENTITY.md +6 -0
  147. package/templates/workspaces/elena/LEARNINGS.md +5 -0
  148. package/templates/workspaces/elena/MEMORY.md +22 -0
  149. package/templates/workspaces/elena/PLAYBOOK.md +16 -0
  150. package/templates/workspaces/elena/SOUL.md +1 -1
  151. package/templates/workspaces/elena/TOOLS.md +15 -0
  152. package/templates/workspaces/elena/USER.md +13 -0
  153. package/templates/workspaces/eva/AGENTS.md +59 -0
  154. package/templates/workspaces/eva/FEEDBACK.md +11 -0
  155. package/templates/workspaces/eva/HEARTBEAT.md +9 -0
  156. package/templates/workspaces/eva/IDENTITY.md +6 -0
  157. package/templates/workspaces/eva/LEARNINGS.md +5 -0
  158. package/templates/workspaces/eva/MEMORY.md +22 -0
  159. package/templates/workspaces/eva/PLAYBOOK.md +16 -0
  160. package/templates/workspaces/eva/SOUL.md +1 -1
  161. package/templates/workspaces/eva/TOOLS.md +15 -0
  162. package/templates/workspaces/eva/USER.md +13 -0
  163. package/templates/workspaces/felix/AGENTS.md +52 -0
  164. package/templates/workspaces/felix/FEEDBACK.md +11 -0
  165. package/templates/workspaces/felix/HEARTBEAT.md +5 -0
  166. package/templates/workspaces/felix/IDENTITY.md +6 -0
  167. package/templates/workspaces/felix/LEARNINGS.md +17 -0
  168. package/templates/workspaces/felix/MEMORY.md +22 -0
  169. package/templates/workspaces/felix/PLAYBOOK.md +16 -0
  170. package/templates/workspaces/felix/SOUL.md +1 -1
  171. package/templates/workspaces/felix/USER.md +13 -0
  172. package/templates/workspaces/felix/fidelia-psychology.html +1594 -0
  173. package/templates/workspaces/felix/task.txt +164 -0
  174. package/templates/workspaces/hannah/AGENTS.md +59 -0
  175. package/templates/workspaces/hannah/FEEDBACK.md +12 -0
  176. package/templates/workspaces/hannah/HEARTBEAT.md +5 -0
  177. package/templates/workspaces/hannah/IDENTITY.md +6 -0
  178. package/templates/workspaces/hannah/LEARNINGS.md +6 -0
  179. package/templates/workspaces/hannah/MEMORY.md +22 -0
  180. package/templates/workspaces/hannah/PLAYBOOK.md +16 -0
  181. package/templates/workspaces/hannah/SOUL.md +1 -1
  182. package/templates/workspaces/hannah/TOOLS.md +15 -0
  183. package/templates/workspaces/hannah/USER.md +17 -0
  184. package/templates/workspaces/isaac/AGENTS.md +52 -0
  185. package/templates/workspaces/isaac/FEEDBACK.md +12 -0
  186. package/templates/workspaces/isaac/HEARTBEAT.md +9 -0
  187. package/templates/workspaces/isaac/IDENTITY.md +6 -0
  188. package/templates/workspaces/isaac/LEARNINGS.md +6 -0
  189. package/templates/workspaces/isaac/MEMORY.md +22 -0
  190. package/templates/workspaces/isaac/PLAYBOOK.md +16 -0
  191. package/templates/workspaces/isaac/SOUL.md +1 -1
  192. package/templates/workspaces/isaac/USER.md +17 -0
  193. package/templates/workspaces/isaac/skills/claude-code/SKILL.md +38 -0
  194. package/templates/workspaces/isaac/skills/claude-code/claude_code +55 -0
  195. package/templates/workspaces/logan/AGENTS.md +59 -0
  196. package/templates/workspaces/logan/FEEDBACK.md +11 -0
  197. package/templates/workspaces/logan/HEARTBEAT.md +9 -0
  198. package/templates/workspaces/logan/IDENTITY.md +6 -0
  199. package/templates/workspaces/logan/LEARNINGS.md +5 -0
  200. package/templates/workspaces/logan/MEMORY.md +22 -0
  201. package/templates/workspaces/logan/PLAYBOOK.md +16 -0
  202. package/templates/workspaces/logan/SOUL.md +1 -1
  203. package/templates/workspaces/logan/TOOLS.md +15 -0
  204. package/templates/workspaces/logan/USER.md +13 -0
  205. package/templates/workspaces/maxxipro/AGENTS.md +29 -0
  206. package/templates/workspaces/maxxipro/FEEDBACK.md +19 -0
  207. package/templates/workspaces/maxxipro/HEARTBEAT.md +22 -0
  208. package/templates/workspaces/maxxipro/IDENTITY.md +35 -0
  209. package/templates/workspaces/maxxipro/KNOWLEDGE.md +335 -0
  210. package/templates/workspaces/maxxipro/LEARNINGS.md +47 -0
  211. package/templates/workspaces/maxxipro/MEMORY.md +60 -0
  212. package/templates/workspaces/maxxipro/OUTREACH_TEMPLATES.md +143 -0
  213. package/templates/workspaces/maxxipro/PLAYBOOK.md +81 -0
  214. package/templates/workspaces/maxxipro/SOUL.md +146 -0
  215. package/templates/workspaces/maxxipro/TOOLS.md +81 -0
  216. package/templates/workspaces/maxxipro/USER.md +40 -0
  217. package/templates/workspaces/morgan/AGENTS.md +59 -0
  218. package/templates/workspaces/morgan/FEEDBACK.md +19 -0
  219. package/templates/workspaces/morgan/HEARTBEAT.md +5 -0
  220. package/templates/workspaces/morgan/IDENTITY.md +6 -0
  221. package/templates/workspaces/morgan/LEARNINGS.md +18 -0
  222. package/templates/workspaces/morgan/MEMORY.md +22 -0
  223. package/templates/workspaces/morgan/PLAYBOOK.md +16 -0
  224. package/templates/workspaces/morgan/SOUL.md +1 -1
  225. package/templates/workspaces/morgan/TOOLS.md +15 -0
  226. package/templates/workspaces/morgan/USER.md +13 -0
  227. package/templates/workspaces/nadia/AGENTS.md +59 -0
  228. package/templates/workspaces/nadia/FEEDBACK.md +12 -0
  229. package/templates/workspaces/nadia/HEARTBEAT.md +5 -0
  230. package/templates/workspaces/nadia/IDENTITY.md +6 -0
  231. package/templates/workspaces/nadia/LEARNINGS.md +6 -0
  232. package/templates/workspaces/nadia/MEMORY.md +22 -0
  233. package/templates/workspaces/nadia/PLAYBOOK.md +16 -0
  234. package/templates/workspaces/nadia/SOUL.md +1 -1
  235. package/templates/workspaces/nadia/TOOLS.md +15 -0
  236. package/templates/workspaces/nadia/USER.md +13 -0
  237. package/templates/workspaces/nate/AGENTS.md +24 -0
  238. package/templates/workspaces/nate/FEEDBACK.md +12 -0
  239. package/templates/workspaces/nate/HEARTBEAT.md +33 -0
  240. package/templates/workspaces/nate/IDENTITY.md +15 -0
  241. package/templates/workspaces/nate/LEARNINGS.md +33 -0
  242. package/templates/workspaces/nate/MEMORY.md +39 -0
  243. package/templates/workspaces/nate/PLAYBOOK.md +160 -0
  244. package/templates/workspaces/nate/SOUL.md +50 -0
  245. package/templates/workspaces/nate/TOOLS.md +111 -0
  246. package/templates/workspaces/nate/USER.md +32 -0
  247. package/templates/workspaces/olivia/.last-openclaw-version +1 -0
  248. package/templates/workspaces/olivia/.npmrc.tmp +0 -0
  249. package/templates/workspaces/olivia/AGENTS.md +77 -0
  250. package/templates/workspaces/olivia/ALPHA_CODING_BENCHMARK.txt +148 -0
  251. package/templates/workspaces/olivia/ALPHA_MODEL_GUIDE.md +393 -0
  252. package/templates/workspaces/olivia/FEEDBACK.md +13 -0
  253. package/templates/workspaces/olivia/HEADTOHEAD_BENCHMARK.txt +1289 -0
  254. package/templates/workspaces/olivia/HEARTBEAT.md +267 -0
  255. package/templates/workspaces/olivia/IDENTITY.md +6 -0
  256. package/templates/workspaces/olivia/LEARNINGS.md +708 -0
  257. package/templates/workspaces/olivia/MEMORY.md +202 -0
  258. package/templates/workspaces/olivia/MISSION_CONTROL_DESIGN_SPEC_v1.md +1143 -0
  259. package/templates/workspaces/olivia/MVP-COMPLETION-SUMMARY.md +175 -0
  260. package/templates/workspaces/olivia/NETWORK_IMPLEMENTATION_PLAN.md +1556 -0
  261. package/templates/workspaces/olivia/NEW_NODES_BENCHMARK.txt +947 -0
  262. package/templates/workspaces/olivia/PLAYBOOK.md +42 -0
  263. package/templates/workspaces/olivia/SELF-HEALING-COMPLETE.md +150 -0
  264. package/templates/workspaces/olivia/SOUL.md +8 -8
  265. package/templates/workspaces/olivia/TOOLS.md +15 -0
  266. package/templates/workspaces/olivia/USER.md +17 -0
  267. package/templates/workspaces/olivia/alicefleet-supabase-credentials.md +50 -0
  268. package/templates/workspaces/olivia/dzombo-copy-rewrite.md +115 -0
  269. package/templates/workspaces/olivia/dzombo-implementation-plan.md +1248 -0
  270. package/templates/workspaces/olivia/fidelia-psychology.html +1594 -0
  271. package/templates/workspaces/olivia/lead_debug.png +0 -0
  272. package/templates/workspaces/olivia/minimatch-10.2.4.tgz +0 -0
  273. package/templates/workspaces/olivia/operation-bllm-research.md +157 -0
  274. package/templates/workspaces/olivia/qa-audit-mission-control-v2.md +538 -0
  275. package/templates/workspaces/olivia/roofmaxx_logo.svg +1 -0
  276. package/templates/workspaces/olivia/roofmaxx_social.jpg +0 -0
  277. package/templates/workspaces/olivia/skills/1password/SKILL.md +53 -0
  278. package/templates/workspaces/olivia/skills/1password/_meta.json +6 -0
  279. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/README.md +57 -0
  280. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/SKILL.md +534 -0
  281. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/_meta.json +6 -0
  282. package/templates/workspaces/olivia/skills/agent-security/SKILL.md +69 -0
  283. package/templates/workspaces/olivia/skills/agent-security/_meta.json +6 -0
  284. package/templates/workspaces/olivia/skills/agentic-security-audit/SKILL.md +855 -0
  285. package/templates/workspaces/olivia/skills/agentic-security-audit/_meta.json +6 -0
  286. package/templates/workspaces/olivia/skills/ai-automation-consulting/SKILL.md +67 -0
  287. package/templates/workspaces/olivia/skills/ai-automation-consulting/_meta.json +6 -0
  288. package/templates/workspaces/olivia/skills/ai-automation-consulting/skill.json +12 -0
  289. package/templates/workspaces/olivia/skills/ai-presentation-maker/SKILL.md +1104 -0
  290. package/templates/workspaces/olivia/skills/ai-presentation-maker/_meta.json +6 -0
  291. package/templates/workspaces/olivia/skills/ai-productivity-audit/SKILL.md +181 -0
  292. package/templates/workspaces/olivia/skills/ai-productivity-audit/_meta.json +6 -0
  293. package/templates/workspaces/olivia/skills/ai-researcher/README.md +31 -0
  294. package/templates/workspaces/olivia/skills/ai-researcher/SKILL.md +59 -0
  295. package/templates/workspaces/olivia/skills/ai-researcher/_meta.json +6 -0
  296. package/templates/workspaces/olivia/skills/ai-seo-writer/README.md +19 -0
  297. package/templates/workspaces/olivia/skills/ai-seo-writer/SKILL.md +100 -0
  298. package/templates/workspaces/olivia/skills/ai-seo-writer/_meta.json +6 -0
  299. package/templates/workspaces/olivia/skills/analytics-tracking-2/SKILL.md +309 -0
  300. package/templates/workspaces/olivia/skills/analytics-tracking-2/_meta.json +6 -0
  301. package/templates/workspaces/olivia/skills/api-doc-writer/SKILL.md +232 -0
  302. package/templates/workspaces/olivia/skills/api-doc-writer/_meta.json +6 -0
  303. package/templates/workspaces/olivia/skills/api-generator/SKILL.md +49 -0
  304. package/templates/workspaces/olivia/skills/api-generator/_meta.json +6 -0
  305. package/templates/workspaces/olivia/skills/api-generator/tips.md +10 -0
  306. package/templates/workspaces/olivia/skills/apple-notes/SKILL.md +50 -0
  307. package/templates/workspaces/olivia/skills/apple-notes/_meta.json +6 -0
  308. package/templates/workspaces/olivia/skills/apple-reminders/SKILL.md +67 -0
  309. package/templates/workspaces/olivia/skills/apple-reminders/_meta.json +6 -0
  310. package/templates/workspaces/olivia/skills/automation-workflows/SKILL.md +267 -0
  311. package/templates/workspaces/olivia/skills/automation-workflows/_meta.json +6 -0
  312. package/templates/workspaces/olivia/skills/autoresearch/SKILL.md +46 -0
  313. package/templates/workspaces/olivia/skills/autoresearch/aria_write.py +148 -0
  314. package/templates/workspaces/olivia/skills/autoresearch/autoresearch.py +75 -0
  315. package/templates/workspaces/olivia/skills/azure-devops/SKILL.md +115 -0
  316. package/templates/workspaces/olivia/skills/azure-devops/_meta.json +6 -0
  317. package/templates/workspaces/olivia/skills/blogwatcher/SKILL.md +46 -0
  318. package/templates/workspaces/olivia/skills/blogwatcher/_meta.json +6 -0
  319. package/templates/workspaces/olivia/skills/blucli/SKILL.md +27 -0
  320. package/templates/workspaces/olivia/skills/blucli/_meta.json +6 -0
  321. package/templates/workspaces/olivia/skills/check-analytics/SKILL.md +92 -0
  322. package/templates/workspaces/olivia/skills/check-analytics/_meta.json +6 -0
  323. package/templates/workspaces/olivia/skills/cloud-architect/SKILL.md +89 -0
  324. package/templates/workspaces/olivia/skills/cloud-architect/_meta.json +6 -0
  325. package/templates/workspaces/olivia/skills/cloud-infra-automation/SKILL.md +50 -0
  326. package/templates/workspaces/olivia/skills/cloud-infra-automation/_meta.json +6 -0
  327. package/templates/workspaces/olivia/skills/cloud-storage/SKILL.md +61 -0
  328. package/templates/workspaces/olivia/skills/cloud-storage/_meta.json +6 -0
  329. package/templates/workspaces/olivia/skills/cloud-storage/auth.md +97 -0
  330. package/templates/workspaces/olivia/skills/cloud-storage/costs.md +88 -0
  331. package/templates/workspaces/olivia/skills/cloud-storage/providers.md +55 -0
  332. package/templates/workspaces/olivia/skills/copywriting-pro/SKILL.md +107 -0
  333. package/templates/workspaces/olivia/skills/copywriting-pro/_meta.json +6 -0
  334. package/templates/workspaces/olivia/skills/data-analyst-pro/SKILL.md +21 -0
  335. package/templates/workspaces/olivia/skills/data-analyst-pro/_meta.json +6 -0
  336. package/templates/workspaces/olivia/skills/database-designer/README.md +388 -0
  337. package/templates/workspaces/olivia/skills/database-designer/SKILL.md +66 -0
  338. package/templates/workspaces/olivia/skills/database-designer/_meta.json +6 -0
  339. package/templates/workspaces/olivia/skills/database-designer/index_optimizer.py +926 -0
  340. package/templates/workspaces/olivia/skills/database-designer/migration_generator.py +1199 -0
  341. package/templates/workspaces/olivia/skills/database-designer/schema_analyzer.py +982 -0
  342. package/templates/workspaces/olivia/skills/deploy-agent/SKILL.md +255 -0
  343. package/templates/workspaces/olivia/skills/deploy-agent/_meta.json +6 -0
  344. package/templates/workspaces/olivia/skills/devops-automation-pack/SKILL.md +72 -0
  345. package/templates/workspaces/olivia/skills/devops-automation-pack/_meta.json +6 -0
  346. package/templates/workspaces/olivia/skills/devops-automation-pack/deploy.sh +0 -0
  347. package/templates/workspaces/olivia/skills/financial-analysis-agent/SKILL.md +489 -0
  348. package/templates/workspaces/olivia/skills/financial-analysis-agent/_meta.json +6 -0
  349. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/README.md +72 -0
  350. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/SKILL.md +226 -0
  351. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/_meta.json +6 -0
  352. package/templates/workspaces/olivia/skills/gifgrep/SKILL.md +47 -0
  353. package/templates/workspaces/olivia/skills/gifgrep/_meta.json +6 -0
  354. package/templates/workspaces/olivia/skills/github/SKILL.md +47 -0
  355. package/templates/workspaces/olivia/skills/github/_meta.json +6 -0
  356. package/templates/workspaces/olivia/skills/gog/SKILL.md +36 -0
  357. package/templates/workspaces/olivia/skills/gog/_meta.json +6 -0
  358. package/templates/workspaces/olivia/skills/growth-strategy-hub/SKILL.md +135 -0
  359. package/templates/workspaces/olivia/skills/growth-strategy-hub/_meta.json +6 -0
  360. package/templates/workspaces/olivia/skills/growth-strategy-hub/metadata.json +4 -0
  361. package/templates/workspaces/olivia/skills/hetzner-cloud/SKILL.md +130 -0
  362. package/templates/workspaces/olivia/skills/hetzner-cloud/_meta.json +6 -0
  363. package/templates/workspaces/olivia/skills/himalaya/SKILL.md +217 -0
  364. package/templates/workspaces/olivia/skills/himalaya/_meta.json +6 -0
  365. package/templates/workspaces/olivia/skills/hotel-recommendation/SKILL.md +117 -0
  366. package/templates/workspaces/olivia/skills/hotel-recommendation/_meta.json +6 -0
  367. package/templates/workspaces/olivia/skills/hr-policy-generator/SKILL.md +54 -0
  368. package/templates/workspaces/olivia/skills/hr-policy-generator/_meta.json +6 -0
  369. package/templates/workspaces/olivia/skills/human-writing/SKILL.md +41 -0
  370. package/templates/workspaces/olivia/skills/human-writing/_meta.json +6 -0
  371. package/templates/workspaces/olivia/skills/imsg/SKILL.md +25 -0
  372. package/templates/workspaces/olivia/skills/imsg/_meta.json +6 -0
  373. package/templates/workspaces/olivia/skills/in-depth-research/SKILL.md +124 -0
  374. package/templates/workspaces/olivia/skills/in-depth-research/_meta.json +6 -0
  375. package/templates/workspaces/olivia/skills/in-depth-research/methodology.md +75 -0
  376. package/templates/workspaces/olivia/skills/in-depth-research/output-formats.md +168 -0
  377. package/templates/workspaces/olivia/skills/in-depth-research/sources.md +80 -0
  378. package/templates/workspaces/olivia/skills/javascript-skills/README.md +71 -0
  379. package/templates/workspaces/olivia/skills/javascript-skills/SKILL.md +746 -0
  380. package/templates/workspaces/olivia/skills/javascript-skills/_meta.json +6 -0
  381. package/templates/workspaces/olivia/skills/leadership-strategy-playbook/SKILL.md +147 -0
  382. package/templates/workspaces/olivia/skills/leadership-strategy-playbook/_meta.json +6 -0
  383. package/templates/workspaces/olivia/skills/market-research-agent/README.md +29 -0
  384. package/templates/workspaces/olivia/skills/market-research-agent/SKILL.md +52 -0
  385. package/templates/workspaces/olivia/skills/market-research-agent/_meta.json +6 -0
  386. package/templates/workspaces/olivia/skills/marketing-analytics/SKILL.md +74 -0
  387. package/templates/workspaces/olivia/skills/marketing-analytics/_meta.json +6 -0
  388. package/templates/workspaces/olivia/skills/marketing-master-io/SKILL.md +125 -0
  389. package/templates/workspaces/olivia/skills/marketing-master-io/_meta.json +6 -0
  390. package/templates/workspaces/olivia/skills/marketing-strategy-pmm/SKILL.md +398 -0
  391. package/templates/workspaces/olivia/skills/marketing-strategy-pmm/_meta.json +6 -0
  392. package/templates/workspaces/olivia/skills/meta-ads-analytics/SKILL.md +53 -0
  393. package/templates/workspaces/olivia/skills/meta-ads-analytics/_meta.json +6 -0
  394. package/templates/workspaces/olivia/skills/obsidian/SKILL.md +55 -0
  395. package/templates/workspaces/olivia/skills/obsidian/_meta.json +6 -0
  396. package/templates/workspaces/olivia/skills/openclaw-accounting/SKILL.md +125 -0
  397. package/templates/workspaces/olivia/skills/openclaw-accounting/_meta.json +6 -0
  398. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANGELOG.md +35 -0
  399. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANNELLOG.md +73 -0
  400. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/README.md +161 -0
  401. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/SKILL.md +130 -0
  402. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/_meta.json +6 -0
  403. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/config.json +36 -0
  404. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/metadata.json +19 -0
  405. package/templates/workspaces/olivia/skills/openhue/SKILL.md +30 -0
  406. package/templates/workspaces/olivia/skills/openhue/_meta.json +6 -0
  407. package/templates/workspaces/olivia/skills/orgx-operations-agent/SKILL.md +41 -0
  408. package/templates/workspaces/olivia/skills/orgx-operations-agent/_meta.json +6 -0
  409. package/templates/workspaces/olivia/skills/outreach/SKILL.md +84 -0
  410. package/templates/workspaces/olivia/skills/outreach/_meta.json +6 -0
  411. package/templates/workspaces/olivia/skills/outreach/by-type.md +166 -0
  412. package/templates/workspaces/olivia/skills/outreach/templates.md +154 -0
  413. package/templates/workspaces/olivia/skills/outreach/tracking.md +145 -0
  414. package/templates/workspaces/olivia/skills/persona-hr-coordinator/SKILL.md +38 -0
  415. package/templates/workspaces/olivia/skills/persona-hr-coordinator/_meta.json +6 -0
  416. package/templates/workspaces/olivia/skills/personal-productivity/SKILL.md +161 -0
  417. package/templates/workspaces/olivia/skills/personal-productivity/_meta.json +6 -0
  418. package/templates/workspaces/olivia/skills/personal-productivity/index.js +363 -0
  419. package/templates/workspaces/olivia/skills/personal-productivity/package.json +15 -0
  420. package/templates/workspaces/olivia/skills/personal-travel/README.md +34 -0
  421. package/templates/workspaces/olivia/skills/personal-travel/SKILL.md +46 -0
  422. package/templates/workspaces/olivia/skills/personal-travel/_meta.json +6 -0
  423. package/templates/workspaces/olivia/skills/presentation-html-generator-skill/SKILL.md +185 -0
  424. package/templates/workspaces/olivia/skills/presentation-html-generator-skill/_meta.json +6 -0
  425. package/templates/workspaces/olivia/skills/product-manager/SKILL.md +77 -0
  426. package/templates/workspaces/olivia/skills/product-manager/_meta.json +6 -0
  427. package/templates/workspaces/olivia/skills/quant-strategy/SKILL.md +28 -0
  428. package/templates/workspaces/olivia/skills/quant-strategy/_meta.json +6 -0
  429. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/README.md +29 -0
  430. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/SKILL.md +45 -0
  431. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/_meta.json +6 -0
  432. package/templates/workspaces/olivia/skills/security-auditor/SKILL.md +399 -0
  433. package/templates/workspaces/olivia/skills/security-auditor/_meta.json +6 -0
  434. package/templates/workspaces/olivia/skills/security-hardening/SKILL.md +296 -0
  435. package/templates/workspaces/olivia/skills/security-hardening/_meta.json +6 -0
  436. package/templates/workspaces/olivia/skills/security-scanner/SKILL.md +67 -0
  437. package/templates/workspaces/olivia/skills/security-scanner/_meta.json +6 -0
  438. package/templates/workspaces/olivia/skills/seo-optimization/SKILL.md +31 -0
  439. package/templates/workspaces/olivia/skills/seo-optimization/_meta.json +6 -0
  440. package/templates/workspaces/olivia/skills/service-booking/SKILL.md +193 -0
  441. package/templates/workspaces/olivia/skills/service-booking/_meta.json +6 -0
  442. package/templates/workspaces/olivia/skills/sme-hr-automation/SKILL.md +131 -0
  443. package/templates/workspaces/olivia/skills/sme-hr-automation/_meta.json +6 -0
  444. package/templates/workspaces/olivia/skills/social-media-scheduler/README.md +29 -0
  445. package/templates/workspaces/olivia/skills/social-media-scheduler/SKILL.md +49 -0
  446. package/templates/workspaces/olivia/skills/social-media-scheduler/_meta.json +6 -0
  447. package/templates/workspaces/olivia/skills/sonoscli/SKILL.md +26 -0
  448. package/templates/workspaces/olivia/skills/sonoscli/_meta.json +6 -0
  449. package/templates/workspaces/olivia/skills/strategy-advisor/SKILL.md +33 -0
  450. package/templates/workspaces/olivia/skills/strategy-advisor/_meta.json +6 -0
  451. package/templates/workspaces/olivia/skills/summarize/SKILL.md +49 -0
  452. package/templates/workspaces/olivia/skills/summarize/_meta.json +6 -0
  453. package/templates/workspaces/olivia/skills/things-mac/SKILL.md +61 -0
  454. package/templates/workspaces/olivia/skills/things-mac/_meta.json +6 -0
  455. package/templates/workspaces/olivia/skills/travel-itinerary-planner/SKILL.md +121 -0
  456. package/templates/workspaces/olivia/skills/travel-itinerary-planner/_meta.json +6 -0
  457. package/templates/workspaces/olivia/skills/travel-manager/SKILL.md +36 -0
  458. package/templates/workspaces/olivia/skills/travel-manager/_meta.json +6 -0
  459. package/templates/workspaces/olivia/skills/travel-planning/SKILL.md +238 -0
  460. package/templates/workspaces/olivia/skills/travel-planning/_meta.json +6 -0
  461. package/templates/workspaces/olivia/skills/travel-planning/booking-guide.md +91 -0
  462. package/templates/workspaces/olivia/skills/travel-planning/memory-template.md +111 -0
  463. package/templates/workspaces/olivia/skills/travel-planning/multi-city.md +131 -0
  464. package/templates/workspaces/olivia/skills/travel-planning/packing-templates.md +155 -0
  465. package/templates/workspaces/olivia/skills/travel-planning/setup.md +66 -0
  466. package/templates/workspaces/olivia/skills/update-it-all/SKILL.md +143 -0
  467. package/templates/workspaces/olivia/skills/update-it-all/_meta.json +6 -0
  468. package/templates/workspaces/olivia/skills/voice/SKILL.md +62 -0
  469. package/templates/workspaces/olivia/skills/weather/SKILL.md +49 -0
  470. package/templates/workspaces/olivia/skills/weather/_meta.json +6 -0
  471. package/templates/workspaces/olivia/skills/web-researcher/SKILL.md +21 -0
  472. package/templates/workspaces/olivia/skills/web-researcher/_meta.json +6 -0
  473. package/templates/workspaces/olivia/skills/website-seo/SKILL.md +284 -0
  474. package/templates/workspaces/olivia/skills/website-seo/_meta.json +6 -0
  475. package/templates/workspaces/olivia/stripe-welcome-n8n.json +103 -0
  476. package/templates/workspaces/olivia/test2.wav.wav +0 -0
  477. package/templates/workspaces/olivia/test_speech.json +1 -0
  478. package/templates/workspaces/olivia/test_speech.srt +0 -0
  479. package/templates/workspaces/olivia/test_speech.tsv +1 -0
  480. package/templates/workspaces/olivia/test_speech.txt +0 -0
  481. package/templates/workspaces/olivia/test_speech.vtt +2 -0
  482. package/templates/workspaces/owen/AGENTS.md +59 -0
  483. package/templates/workspaces/owen/FEEDBACK.md +12 -0
  484. package/templates/workspaces/owen/HEARTBEAT.md +5 -0
  485. package/templates/workspaces/owen/IDENTITY.md +6 -0
  486. package/templates/workspaces/owen/LEARNINGS.md +46 -0
  487. package/templates/workspaces/owen/MEMORY.md +22 -0
  488. package/templates/workspaces/owen/PLAYBOOK.md +16 -0
  489. package/templates/workspaces/owen/SOUL.md +1 -1
  490. package/templates/workspaces/owen/TOOLS.md +15 -0
  491. package/templates/workspaces/owen/USER.md +17 -0
  492. package/templates/workspaces/parker/AGENTS.md +59 -0
  493. package/templates/workspaces/parker/FEEDBACK.md +11 -0
  494. package/templates/workspaces/parker/HEARTBEAT.md +5 -0
  495. package/templates/workspaces/parker/IDENTITY.md +6 -0
  496. package/templates/workspaces/parker/LEARNINGS.md +17 -0
  497. package/templates/workspaces/parker/MEMORY.md +22 -0
  498. package/templates/workspaces/parker/PLAYBOOK.md +16 -0
  499. package/templates/workspaces/parker/SOUL.md +1 -1
  500. package/templates/workspaces/parker/TOOLS.md +15 -0
  501. package/templates/workspaces/parker/USER.md +13 -0
  502. package/templates/workspaces/quinn/AGENTS.md +52 -0
  503. package/templates/workspaces/quinn/FEEDBACK.md +11 -0
  504. package/templates/workspaces/quinn/HEARTBEAT.md +5 -0
  505. package/templates/workspaces/quinn/IDENTITY.md +6 -0
  506. package/templates/workspaces/quinn/LEARNINGS.md +35 -0
  507. package/templates/workspaces/quinn/MEMORY.md +22 -0
  508. package/templates/workspaces/quinn/PLAYBOOK.md +16 -0
  509. package/templates/workspaces/quinn/SOUL.md +1 -1
  510. package/templates/workspaces/quinn/USER.md +17 -0
  511. package/templates/workspaces/quinn/alice-login-page.png +0 -0
  512. package/templates/workspaces/rowan/AGENTS.md +59 -0
  513. package/templates/workspaces/rowan/FEEDBACK.md +12 -0
  514. package/templates/workspaces/rowan/HEARTBEAT.md +5 -0
  515. package/templates/workspaces/rowan/IDENTITY.md +6 -0
  516. package/templates/workspaces/rowan/LEARNINGS.md +12 -0
  517. package/templates/workspaces/rowan/MEMORY.md +22 -0
  518. package/templates/workspaces/rowan/PLAYBOOK.md +16 -0
  519. package/templates/workspaces/rowan/SOUL.md +1 -1
  520. package/templates/workspaces/rowan/USER.md +17 -0
  521. package/templates/workspaces/selena/AGENTS.md +59 -0
  522. package/templates/workspaces/selena/FEEDBACK.md +12 -0
  523. package/templates/workspaces/selena/HEARTBEAT.md +5 -0
  524. package/templates/workspaces/selena/IDENTITY.md +6 -0
  525. package/templates/workspaces/selena/LEARNINGS.md +24 -0
  526. package/templates/workspaces/selena/MEMORY.md +22 -0
  527. package/templates/workspaces/selena/PLAYBOOK.md +16 -0
  528. package/templates/workspaces/selena/SOUL.md +1 -1
  529. package/templates/workspaces/selena/USER.md +17 -0
  530. package/templates/workspaces/selena/kids-ai-security-compliance-plan.md +791 -0
  531. package/templates/workspaces/selena/kidspark-coppa-compliance-audit.md +866 -0
  532. package/templates/workspaces/sloane/AGENTS.md +59 -0
  533. package/templates/workspaces/sloane/FEEDBACK.md +12 -0
  534. package/templates/workspaces/sloane/HEARTBEAT.md +9 -0
  535. package/templates/workspaces/sloane/IDENTITY.md +6 -0
  536. package/templates/workspaces/sloane/LEARNINGS.md +6 -0
  537. package/templates/workspaces/sloane/MEMORY.md +22 -0
  538. package/templates/workspaces/sloane/PLAYBOOK.md +16 -0
  539. package/templates/workspaces/sloane/SOUL.md +1 -1
  540. package/templates/workspaces/sloane/TOOLS.md +15 -0
  541. package/templates/workspaces/sloane/USER.md +13 -0
  542. package/templates/workspaces/smoketestagent/AGENTS.md +52 -0
  543. package/templates/workspaces/smoketestagent/FEEDBACK.md +3 -0
  544. package/templates/workspaces/smoketestagent/HEARTBEAT.md +14 -0
  545. package/templates/workspaces/smoketestagent/IDENTITY.md +6 -0
  546. package/templates/workspaces/smoketestagent/LEARNINGS.md +3 -0
  547. package/templates/workspaces/smoketestagent/MEMORY.md +24 -0
  548. package/templates/workspaces/smoketestagent/PLAYBOOK.md +7 -0
  549. package/templates/workspaces/smoketestagent/SOUL.md +32 -0
  550. package/templates/workspaces/smoketestagent/TOOLS.md +13 -0
  551. package/templates/workspaces/smoketestagent/USER.md +5 -0
  552. package/templates/workspaces/sophie/AGENTS.md +59 -0
  553. package/templates/workspaces/sophie/FEEDBACK.md +12 -0
  554. package/templates/workspaces/sophie/HEARTBEAT.md +9 -0
  555. package/templates/workspaces/sophie/IDENTITY.md +6 -0
  556. package/templates/workspaces/sophie/LEARNINGS.md +6 -0
  557. package/templates/workspaces/sophie/MEMORY.md +22 -0
  558. package/templates/workspaces/sophie/PLAYBOOK.md +16 -0
  559. package/templates/workspaces/sophie/SOUL.md +1 -1
  560. package/templates/workspaces/sophie/TOOLS.md +15 -0
  561. package/templates/workspaces/sophie/USER.md +17 -0
  562. package/templates/workspaces/tommy/AGENTS.md +59 -0
  563. package/templates/workspaces/tommy/FEEDBACK.md +12 -0
  564. package/templates/workspaces/tommy/HEARTBEAT.md +9 -0
  565. package/templates/workspaces/tommy/IDENTITY.md +6 -0
  566. package/templates/workspaces/tommy/LEARNINGS.md +6 -0
  567. package/templates/workspaces/tommy/MEMORY.md +22 -0
  568. package/templates/workspaces/tommy/PLAYBOOK.md +16 -0
  569. package/templates/workspaces/tommy/SOUL.md +1 -1
  570. package/templates/workspaces/tommy/TOOLS.md +15 -0
  571. package/templates/workspaces/tommy/USER.md +17 -0
  572. package/templates/workspaces/uma/AGENTS.md +59 -0
  573. package/templates/workspaces/uma/FEEDBACK.md +11 -0
  574. package/templates/workspaces/uma/HEARTBEAT.md +5 -0
  575. package/templates/workspaces/uma/IDENTITY.md +6 -0
  576. package/templates/workspaces/uma/LEARNINGS.md +11 -0
  577. package/templates/workspaces/uma/MEMORY.md +22 -0
  578. package/templates/workspaces/uma/PLAYBOOK.md +16 -0
  579. package/templates/workspaces/uma/SOUL.md +1 -1
  580. package/templates/workspaces/uma/TOOLS.md +15 -0
  581. 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