{"slug": "show-hn-methadone-sieze-the-means-of-production-from-our-agentic-overlords", "title": "Show HN: Methadone – Sieze the means of production from our agentic overlords", "summary": "A developer released Methadone, an open-source tool that wraps agentic AI coding assistants such as Claude Code and GitHub Copilot CLI with a start-up timeout that grows based on how often and how long the user has run them. Distributed as a Nix function for NixOS and Home-manager configurations and requiring Babashka on the $PATH, Methadone weights each session by age using exponential decay rather than a fixed window, and its optional methadone.stats command reports on the shared usage log instead of wrapping a binary. The developer framed the project around Marx's Theory of Alienation and Naur's \"Programming as Theory Building,\" arguing agentic AI tools can be useful but should not become a crux.", "body_md": "It occurred to me that agentic AI tools can exacerbate the feeling of\nestrangement from ones own work, per Marx's [Theory of Alienation](https://en.wikipedia.org/wiki/Marx%27s_theory_of_alienation), and\nare in direct contradiction to Naur's treatise of [Programming as Theory\nBuilding](https://pablo.rauzy.name/dev/naur1985programming.pdf). (See my [original post](https://hachyderm.io/@xophmeister/116857208020117822) on this, on Mastodon.) Agentic AI\ntools can be useful, but they shouldn't become a crux. So I wrote this\nlittle tool to seize the means of production!\n\nRather than disabling these tools altogether, this script wraps them with a start-up timeout that grows with how much you've leant on them lately -- both how often you've reached for them and how long you've kept them running -- along with a \"helpful\" message to remind you that you should be in control of your own work.\n\nThis is distributed as a [Nix](https://nixos.org) function, that can be used in your NixOS\nconfiguration, for example, like so:\n\n``` js\n{ pkgs, ... }:\n\nlet\n  methadone = pkgs.callPackage ./path/to/methadone.nix { };\nin\n{\n  environment.systemPackages = [\n    (methadone.wrap {\n      package = pkgs.claude-code;\n      binary = \"claude\";\n    })\n\n    # Wrap as many as you like; they share one log\n    (methadone.wrap {\n      package = pkgs.github-copilot-cli;\n      binary = \"copilot\";\n    })\n\n    # Methadone under its own name, which reports rather than wrapping\n    methadone.stats\n  ];\n}\n```\n\nor, with [Home-manager](https://nix-community.github.io/home-manager):\n\n```\nhome-manager.users.YOU.home.packages = [\n  (methadone.wrap {\n    package = pkgs.github-copilot-cli;\n    binary = \"copilot\";\n  })\n];\n```\n\n`methadone.stats` is optional and installs a `methadone` command that\nreports on what the log holds rather than standing in front of anything.\n\nI gotchu, bro. All you need is [Babashka](https://babashka.org) on your `$PATH`, plus a\ndirectory that takes precedence over the binary you want to wrap:\n\n1. \nBuild the single-file script. The sources are a handful of namespaces, but what gets installed is one file, concatenated in dependency order: \n\n```\nbb build\n```\n\n2. \nPut it somewhere permanent: \n\n```\ninstall -Dm755 methadone ~/.local/share/methadone/methadone\n```\n\n3. \nSymlink it into a directory that comes *earlier* in your`$PATH` than\nthe real binary, with that binary's name:\n\n```\nln -s ~/.local/share/methadone/methadone ~/.local/bin/claude\n```\n\n4. \nRinse and repeat for anything else you want to wrap: one symlink each, all pointing at the same script.\n5. \nOptionally, symlink it under its *own* name as well. Invoked as`methadone` , with no agent standing behind it, it reports on the log\nrather than wrapping anything:\n\n```\nln -s ~/.local/share/methadone/methadone ~/.local/bin/methadone\n```\n\nMethadone works out what to run from the name it was invoked as. It\nlooks along `$PATH` for the next binary of that name which isn't itself,\nso the symlink shadows the real `claude` and Methadone finds it\nimmediately behind. There's nothing to configure per binary.\n\nIf that's not what you want -- the real binary isn't on `$PATH`, or you\nwant to wrap it under a different name -- set `METHADONE_BINARY` to its\nfull path and Methadone will use that instead. That's how the Nix route\nworks: the wrapper sets it for you.\n\nThe wait is a function of two things: how often you've launched the tool and how long you've kept it running. Recent use counts for more than old and the whole thing is bounded, so that Methadone never becomes so obstructive that deleting it is the rational move.\n\nMethadone doesn't count usage inside a fixed window. Every session is\nweighted by its age instead, with an exponential decay in which `:window`:\n\nA hard window is a cliff. Under a rolling week, last Monday's marathon\nstops counting *this* Monday and your friction drops for no reason you\nearned; a free pass on a schedule you could learn and time your work\naround. Decaying the weight removes the cliff: old sessions never stop\ncounting, they just matter less.\n\n`:window` is therefore a mean lifetime rather than a cut-off. The\nhalf-life follows from it, \n\nThat gives two decayed totals: a count of launches *integrated* across the session. Writing \n\nIntegrating, rather than weighting the whole span at its start,\ndiscounts the older part of a long session against its newer and it has\na pleasant consequence. A session you never close has \n\nThe two measurements are traded against each other at a fixed rate and added together into a single score:\n\n`:session-equivalent`. That rate and at its default of 30\nminutes reads as a question in English: *How long may a session run\nbefore it counts as another launch?*\n\nBoth terms are needed, because the friction is a start-up cost and nothing else. Once the nag is paid, keeping a session open is free, so counting launches alone makes the cheapest strategy a single session opened when you wake up and abandoned when you go to bed. That is more use of the tool for less friction, which is precisely backwards. The exchange rate decides which habit comes off worst: set it too generously and someone churning through short sessions is punished harder than someone who never closes one at all.\n\nBecause the cost of leaving a session open is only charged the *next*\ntime you start one, Methadone says what it is weighing as it makes you\nwait:\n\n```\nBack for a top-up already, are we?\nLately: 6 launches, 9h 11m running, for a score of 25.\n```\n\nThe scolding escalates with the score as well. A ladder of messages, ordered from mild concern to open denunciation, is spread evenly across the span of the score that the curve actually varies over, which is twice its midpoint; past the top of that span there is nothing harsher left to say, so the sternest of them stands. The words therefore keep pace with the wait rather than repeating one rebuke at every level and a week bad enough to be worth remarking on is remarked upon differently from a quiet one.\n\nConsider the following three habits to make that concrete; each a week's worth, at the point where the decay has settled:\n\n|  |  | Score | \n|---|---|---|\n| A light week | 5 sessions of 20 minutes | 8 | \n| A heavy week | 40 short sessions | 47 | \n| One long session a day | 7 sessions of 8 hours | 119 | \n\nThe last is what the duration term exists to catch. Under the\n[original](https://github.com/Xophmeister/methadone/releases/tag/v0.1.0) formula, which counted launches over a\ncalendar day, it cost exactly the same as the lightest.\n\nThe score is fed through a logistic, of ceiling \n\nGentle while usage is ordinary, steep once it isn't, then levelling off rather than climbing forever.\n\nThat bound is deliberate. The obvious alternative -- keep doubling, as\nthe original did -- reaches hours within a fortnight and a wait long\nenough to be worth circumventing buys no deterrence at all: the bypass\nis a single `rm`. A Methadone that's been deleted measures nothing.\n\nA hard cap has the opposite defect. Past the cap, more usage is free: you've paid the toll, so you may as well carry on. A logistic approaches its ceiling without ever quite reaching it, so there is always a little more to pay.\n\nTwo constants shape the curve and they do (almost) independent jobs.\n\n`:anchors` is a pair of `[score, seconds]` opinions: what a light week\nand a heavy one ought to cost. The steepness and midpoint are *solved\nfor* from them rather than written down, because `0.0645` and `84.04`\nare numbers nobody can sanity-check, whereas \"ten seconds after a light\nweek\" is a judgement you can actually hold. `:max-friction` is the\nceiling, \n\nInverting the logistic gives \n\nThose logarithms are where the configuration guard comes from, too:\ntheir argument is positive and finite only for [If you get it wrong](#if-you-get-it-wrong).\n\nMoving the ceiling barely disturbs the anchored region:\n\n| `:max-friction` | Light week | Heavy week | One long session a day | \n|---|---|---|---|\n| 10 minutes | 9 s | 2 min | 10 min | \n| 20 minutes | 9 s | 2 min | 18 min | \n| 30 minutes | 9 s | 2 min | 26 min | \n\n...and moving the anchors barely disturbs the ceiling's:\n\n| `:anchors` | Light week | Heavy week | One long session a day | \n|---|---|---|---|\n| `[[10 5] [50 60]]` | 4 s | 49 s | 16 min | \n| `[[10 10] [50 120]]` | 9 s | 2 min | 18 min | \n| `[[10 20] [50 240]]` | 18 s | 3 min | 19 min | \n\nSo the anchors set how the everyday feels and the ceiling sets what the\nworst case costs; you can tune either without upsetting the other. All\nfour are settings, described under [Configuration](#configuration).\n\nMethadone runs on its defaults with no configuration at all. To change\nthem, drop a `methadone.edn` in `$XDG_CONFIG_HOME` (usually\n`~/.config/methadone.edn`):\n\n```\n{:window   [14 :days]\n :anchors  [[10 30] [50 300]]\n :log      \"/home/you/.local/state/methadone/log.edn\"}\n```\n\nAnything you leave out keeps its default, so a file need only name what you want to differ.\n\n| Setting | Default | What it is | \n|---|---|---|\n| `:window` | `[7 :days]` | The decay's mean lifetime | \n| `:retention` | `[30 :days]` | How long a session is kept in full before it is reduced to a daily tally | \n| `:heartbeat` | `[60 :seconds]` | How often a running session marks itself alive | \n| `:session-equivalent` | `[30 :minutes]` | Runtime worth as much as one launch | \n| `:max-friction` | `1200` | The longest possible wait, in seconds | \n| `:anchors` | `[[10 10] [50 120]]` | Two `[score seconds]` opinions pinning the curve | \n| `:log` | XDG state path | Where the log lives, if not where it usually would | \n\nSpans of history are given as `[n unit]`, where the unit is one of\n`:ms`, `:seconds`, `:minutes`, `:hours` or `:days`; or as a bare number\nof milliseconds, if you prefer. Waits, being what you actually sit\nthrough, are always plain seconds.\n\n`:retention` must be at least `:window` and Methadone refuses to start\nif it isn't: sessions the friction still counts would otherwise be\nreduced before it could count them and the wait would quietly fall for\nreasons you hadn't asked for. There is no need to set it generously\nbeyond that, though: What is kept past it is the daily tally, which is\nkept for good regardless, so a longer retention buys detail nobody reads\nat the cost of a file rewritten on every heartbeat.\n\nMethadone also reads a `methadone.edn` from each directory in\n`$XDG_CONFIG_DIRS`, after your own. Yours wins, so a NixOS module can\ninstall a policy at `/etc/xdg/methadone.edn` that you remain free to\noverrule.\n\nMethadone refuses to start and says everything that is wrong in one go rather than one fault at a time:\n\n```\nMethadone cannot use its configuration:\n  :windwo is not a setting Methadone has\n  :retention must be a positive span: milliseconds, or [n unit] with unit one of days, hours, minutes, ms, seconds\n  :anchors must rise, cost more than nothing and stay under :max-friction (1200 s)\n```\n\nRefusing outright is deliberate. The alternative -- shrugging and\nfalling back to the defaults -- means a typo can quietly turn Methadone\ninto something that isn't watching you at all, which is exactly the\nfailure you would never notice. The anchor rule earns its keep here in\nparticular: an anchor at or beyond `:max-friction` has no finite\nlogarithm and the wait that falls out of the arithmetic is *zero*.\n\nMethadone keeps a log at `$XDG_STATE_HOME/methadone/log.edn`; usually\n`~/.local/state/methadone/log.edn`. Set `:log` if you would rather it\nlived elsewhere.\n\nIt holds two things. Sessions are recorded in full, one entry per launch\nper wrapped binary, with the moment it began and the moment it ended;\nthese are what the friction is worked out from and they are kept for\n`:retention`. Once a session is older than that it isn't discarded but\nreduced: the detail goes and a tally of the day it began on -- how many\nlaunches, how long altogether -- is added to a history that is kept\nindefinitely.\n\nThe second half costs a few dozen bytes a day and reaches back as far as you have been running Methadone, which is what the report below is drawn from.\n\nInvoked under its own name, with no agent in front of it, Methadone reports rather than wraps:\n\n``` bash\n$ methadone\nThe last 7 days and what it costs you now:\n  binary   launches    running   score    wait\n  claude          9    11h 50m      21     20s\n  copilot         2        45m       2      6s\n\nScored by week:\n  earlier    59    46    35    36   now\n```\n\nThe two halves of that table are counted differently, deliberately. Launches and running time are a plain tally over seven whole days, which you can check against your own memory of the week. The score and the wait are decayed by age and are what you would pay for reaching for the tool right now; i.e., the very figures that produced your last nag.\n\nThe trend is scored by week rather than decayed, so that its numbers can\nbe compared with one another and with the rows above them. It draws on\nthe history as well as on the sessions still held in full, so it reaches\nback further than `:retention` does.\n\nUnder Nix, `methadone.stats` installs it. Otherwise it is the symlink in\nstep 5 above.\n\nYep. However, it's *probably* easier to just run it than trying to\ncircumvent it. The idea is to provide enough friction to make you think\ntwice before reaching for agentic AI tools and, hopefully, building a\nhabit of re-engaging with your own work.\n\nDeleting the log is the obvious way round, but the reporting means deleting isn't free: The history goes with it and the history is the half that accrues rather than the half that charges you. A fortnight in, that's no loss at all. A year in and it's a year of knowing what you actually did.\n\nCtrl+C is not one of the ways round it, but nor is it meant to be a trap. During the countdown it abandons the launch outright: Methadone commends you, exits, the agent never starts and nothing is written to the log, so thinking better of it costs nothing and is not held against you next time. What it cannot do is hurry the wait along, there being no agent on the far side of it to hurry towards. Once the agent is running, Methadone ignores Ctrl+C and leaves the agent to answer it, as it is much better placed to know what interrupting it should mean.\n\nTo try to further the point, I've included an example agent instruction\nset to reinforce the idea that you should be in control of your own\nwork. Copy [this](https://github.com/Xophmeister/methadone/blob/main/AGENTS.md.eg) wherever your agent of choice looks for\ninstructions.\n\nI sure did...but maybe not in the way you are thinking. Almost all the\napplication code is hand-written, under Claude's tutelage; part of\nMethadone's remit, despite my better judgement, is as a small project\nfor me to learn Clojure. The only big chunks of code that are generated\nare the tests -- I hate writing tests! -- and the SVG plotter for this\n`README`. The friction model was brainstormed between me and the AI and\nsome of the prose (here and in comments/docstrings) have been expounded\nat my request.", "url": "https://wpnews.pro/news/show-hn-methadone-sieze-the-means-of-production-from-our-agentic-overlords", "canonical_source": "https://github.com/Xophmeister/methadone", "published_at": "2026-09-22 14:13:04+00:00", "updated_at": "2026-09-22 14:24:49.017975+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "developer-tools", "ai-products"], "entities": ["Methadone", "Claude Code", "GitHub Copilot CLI", "Nix", "NixOS", "Home-manager", "Babashka", "Karl Marx"], "alternates": {"html": "https://wpnews.pro/news/show-hn-methadone-sieze-the-means-of-production-from-our-agentic-overlords", "markdown": "https://wpnews.pro/news/show-hn-methadone-sieze-the-means-of-production-from-our-agentic-overlords.md", "text": "https://wpnews.pro/news/show-hn-methadone-sieze-the-means-of-production-from-our-agentic-overlords.txt", "jsonld": "https://wpnews.pro/news/show-hn-methadone-sieze-the-means-of-production-from-our-agentic-overlords.jsonld"}}