Xophmeister/methadone

Seize the means of production from our agentic overlords ✊

Clojure

4

9 commits

updated Sep 19, 2026

See the code

See what people are saying (1)

README

Methadone

CI Status

It occurred to me that agentic AI tools can exacerbate the feeling of estrangement from ones own work, per Marx's Theory of Alienation, and are in direct contradiction to Naur's treatise of Programming as Theory Building. (See my original post on this, on Mastodon.) Agentic AI tools can be useful, but they shouldn't become a crux. So I wrote this little tool to seize the means of production!

Rather 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.

Usage

This is distributed as a Nix function, that can be used in your NixOS configuration, for example, like so:

{ pkgs, ... }:

let
  methadone = pkgs.callPackage ./path/to/methadone.nix { };
in
{
  environment.systemPackages = [
    (methadone.wrap {
      package = pkgs.claude-code;
      binary = "claude";
    })

    # Wrap as many as you like; they share one log
    (methadone.wrap {
      package = pkgs.github-copilot-cli;
      binary = "copilot";
    })

    # Methadone under its own name, which reports rather than wrapping
    methadone.stats
  ];
}

or, with Home-manager:

home-manager.users.YOU.home.packages = [
  (methadone.wrap {
    package = pkgs.github-copilot-cli;
    binary = "copilot";
  })
];

methadone.stats is optional and installs a methadone command that reports on what the log holds rather than standing in front of anything.

I don't use NixOS

I gotchu, bro. All you need is Babashka on your $PATH, plus a directory that takes precedence over the binary you want to wrap:

  1. Build the single-file script. The sources are a handful of namespaces, but what gets installed is one file, concatenated in dependency order:

    bb build
    
  2. Put it somewhere permanent:

    install -Dm755 methadone ~/.local/share/methadone/methadone
    
  3. Symlink it into a directory that comes earlier in your $PATH than the real binary, with that binary's name:

    ln -s ~/.local/share/methadone/methadone ~/.local/bin/claude
    
  4. Rinse and repeat for anything else you want to wrap: one symlink each, all pointing at the same script.

  5. Optionally, symlink it under its own name as well. Invoked as methadone, with no agent standing behind it, it reports on the log rather than wrapping anything:

    ln -s ~/.local/share/methadone/methadone ~/.local/bin/methadone
    

Methadone works out what to run from the name it was invoked as. It looks along $PATH for the next binary of that name which isn't itself, so the symlink shadows the real claude and Methadone finds it immediately behind. There's nothing to configure per binary.

If that's not what you want -- the real binary isn't on $PATH, or you want to wrap it under a different name -- set METHADONE_BINARY to its full path and Methadone will use that instead. That's how the Nix route works: the wrapper sets it for you.

Methodology

The 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.

Nothing expires; it fades

Methadone doesn't count usage inside a fixed window. Every session is weighted by its age instead, with an exponential decay in which $T$ is now and $W$ is the :window:

w(t) = e^\frac{-(T - t)}{W}

The decay of a session's weight with its age

A hard window is a cliff. Under a rolling week, last Monday's marathon stops counting this Monday and your friction drops for no reason you earned; a free pass on a schedule you could learn and time your work around. Decaying the weight removes the cliff: old sessions never stop counting, they just matter less.

:window is therefore a mean lifetime rather than a cut-off. The half-life follows from it, $w(t) = \frac{1}{2}$ at an age of $T - t = W \ln 2$, which a week's window puts at a tad under five days.

That gives two decayed totals: a count of launches $N$ and a time spent $D$. They are weighed differently, because a launch is an instant and takes the weight of its moment, whereas time spent is a span and so is integrated across the session. Writing $s_i$ and $e_i$ for the start and end of each, an open one ending at $T$ for the time being:

N = \sum_i w(s_i)
\qquad
D = \sum_i \int_{s_i}^{e_i} w(t) \, dt = W \sum_i (w(e_i) - w(s_i))

Integrating, rather than weighting the whole span at its start, discounts the older part of a long session against its newer and it has a pleasant consequence. A session you never close has $e_i = T$, so it contributes $W(1 - w(s_i))$: a quantity that approaches one window's worth and never reaches it, however long you leave the thing running. Its old end decays exactly as fast as its new end accrues. Leaving a session open forever is bounded, not infinite.

One number, two habits

The two measurements are traded against each other at a fixed rate and added together into a single score:

u = N + \frac{D}{E}

$E$ is the :session-equivalent. That rate and at its default of 30 minutes reads as a question in English: How long may a session run before it counts as another launch?

Both 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.

Because the cost of leaving a session open is only charged the next time you start one, Methadone says what it is weighing as it makes you wait:

Back for a top-up already, are we?
Lately: 6 launches, 9h 11m running, for a score of 25.

The 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.

Consider the following three habits to make that concrete; each a week's worth, at the point where the decay has settled:

Score
A light week5 sessions of 20 minutes8
A heavy week40 short sessions47
One long session a day7 sessions of 8 hours119

The last is what the duration term exists to catch. Under the original formula, which counted launches over a calendar day, it cost exactly the same as the lightest.

The curve

The score is fed through a logistic, of ceiling $L$, steepness $k$ and midpoint $u_0$:

f(u) = \frac{L}{1 + e^{-k (u - u_0)}}

The wait, against the usage score

Gentle while usage is ordinary, steep once it isn't, then levelling off rather than climbing forever.

That bound is deliberate. The obvious alternative -- keep doubling, as the original did -- reaches hours within a fortnight and a wait long enough to be worth circumventing buys no deterrence at all: the bypass is a single rm. A Methadone that's been deleted measures nothing.

A 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.

Turning the knobs

Two constants shape the curve and they do (almost) independent jobs.

:anchors is a pair of [score, seconds] opinions: what a light week and a heavy one ought to cost. The steepness and midpoint are solved for from them rather than written down, because 0.0645 and 84.04 are numbers nobody can sanity-check, whereas "ten seconds after a light week" is a judgement you can actually hold. :max-friction is the ceiling, $L$.

Inverting the logistic gives $\ln\frac{f}{L - f} = k(u - u_0)$, which is linear in the score, so a pair of anchors $(u_1, f_1)$ and $(u_2, f_2)$ is enough to fix both unknowns:

k = \frac{1}{u_2 - u_1}
    \left( \ln\frac{f_2}{L - f_2} - \ln\frac{f_1}{L - f_1} \right)
\qquad
u_0 = u_1 - \frac{1}{k} \ln\frac{f_1}{L - f_1}

Those logarithms are where the configuration guard comes from, too: their argument is positive and finite only for $0 < f < L$, so an anchor costing nothing, or costing the ceiling or more, leaves nothing to solve. See If you get it wrong.

Moving the ceiling barely disturbs the anchored region:

:max-frictionLight weekHeavy weekOne long session a day
10 minutes9 s2 min10 min
20 minutes9 s2 min18 min
30 minutes9 s2 min26 min

...and moving the anchors barely disturbs the ceiling's:

:anchorsLight weekHeavy weekOne long session a day
[[10 5] [50 60]]4 s49 s16 min
[[10 10] [50 120]]9 s2 min18 min
[[10 20] [50 240]]18 s3 min19 min

So the anchors set how the everyday feels and the ceiling sets what the worst case costs; you can tune either without upsetting the other. All four are settings, described under Configuration.

Configuration

Methadone runs on its defaults with no configuration at all. To change them, drop a methadone.edn in $XDG_CONFIG_HOME (usually ~/.config/methadone.edn):

{:window   [14 :days]
 :anchors  [[10 30] [50 300]]
 :log      "/home/you/.local/state/methadone/log.edn"}

Anything you leave out keeps its default, so a file need only name what you want to differ.

SettingDefaultWhat it is
:window[7 :days]The decay's mean lifetime
:retention[30 :days]How long a session is kept in full before it is reduced to a daily tally
:heartbeat[60 :seconds]How often a running session marks itself alive
:session-equivalent[30 :minutes]Runtime worth as much as one launch
:max-friction1200The longest possible wait, in seconds
:anchors[[10 10] [50 120]]Two [score seconds] opinions pinning the curve
:logXDG state pathWhere the log lives, if not where it usually would

Spans of history are given as [n unit], where the unit is one of :ms, :seconds, :minutes, :hours or :days; or as a bare number of milliseconds, if you prefer. Waits, being what you actually sit through, are always plain seconds.

:retention must be at least :window and Methadone refuses to start if it isn't: sessions the friction still counts would otherwise be reduced before it could count them and the wait would quietly fall for reasons you hadn't asked for. There is no need to set it generously beyond that, though: What is kept past it is the daily tally, which is kept for good regardless, so a longer retention buys detail nobody reads at the cost of a file rewritten on every heartbeat.

System-wide defaults

Methadone also reads a methadone.edn from each directory in $XDG_CONFIG_DIRS, after your own. Yours wins, so a NixOS module can install a policy at /etc/xdg/methadone.edn that you remain free to overrule.

If you get it wrong

Methadone refuses to start and says everything that is wrong in one go rather than one fault at a time:

Methadone cannot use its configuration:
  :windwo is not a setting Methadone has
  :retention must be a positive span: milliseconds, or [n unit] with unit one of days, hours, minutes, ms, seconds
  :anchors must rise, cost more than nothing and stay under :max-friction (1200 s)

Refusing outright is deliberate. The alternative -- shrugging and falling back to the defaults -- means a typo can quietly turn Methadone into something that isn't watching you at all, which is exactly the failure you would never notice. The anchor rule earns its keep here in particular: an anchor at or beyond :max-friction has no finite logarithm and the wait that falls out of the arithmetic is zero.

State

Methadone keeps a log at $XDG_STATE_HOME/methadone/log.edn; usually ~/.local/state/methadone/log.edn. Set :log if you would rather it lived elsewhere.

It holds two things. Sessions are recorded in full, one entry per launch per wrapped binary, with the moment it began and the moment it ended; these are what the friction is worked out from and they are kept for :retention. Once a session is older than that it isn't discarded but reduced: the detail goes and a tally of the day it began on -- how many launches, how long altogether -- is added to a history that is kept indefinitely.

The 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.

Reading it back

Invoked under its own name, with no agent in front of it, Methadone reports rather than wraps:

$ methadone
The last 7 days and what it costs you now:
  binary   launches    running   score    wait
  claude          9    11h 50m      21     20s
  copilot         2        45m       2      6s

Scored by week:
  earlier    59    46    35    36   now

The 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.

The trend is scored by week rather than decayed, so that its numbers can be compared with one another and with the rows above them. It draws on the history as well as on the sessions still held in full, so it reaches back further than :retention does.

Under Nix, methadone.stats installs it. Otherwise it is the symlink in step 5 above.

Isn't this trivial to bypass?

Yep. However, it's probably easier to just run it than trying to circumvent it. The idea is to provide enough friction to make you think twice before reaching for agentic AI tools and, hopefully, building a habit of re-engaging with your own work.

Deleting 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.

Ctrl+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.

Agent instructions

To try to further the point, I've included an example agent instruction set to reinforce the idea that you should be in control of your own work. Copy this wherever your agent of choice looks for instructions.

So... Did you use AI to write this?

I sure did...but maybe not in the way you are thinking. Almost all the application code is hand-written, under Claude's tutelage; part of Methadone's remit, despite my better judgement, is as a small project for me to learn Clojure. The only big chunks of code that are generated are the tests -- I hate writing tests! -- and the SVG plotter for this README. The friction model was brainstormed between me and the AI and some of the prose (here and in comments/docstrings) have been expounded at my request.

Maybe I like the misery

agentic-ai
marx
naur
nix

Contributors

Xophmeister

9 commits

Xophmeister/methadone

Seize the means of production from our agentic overlords ✊

Clojure

4

9 commits

updated Sep 19, 2026

See the code

See what people are saying (1)

README

Methadone

CI Status

It occurred to me that agentic AI tools can exacerbate the feeling of estrangement from ones own work, per Marx's Theory of Alienation, and are in direct contradiction to Naur's treatise of Programming as Theory Building. (See my original post on this, on Mastodon.) Agentic AI tools can be useful, but they shouldn't become a crux. So I wrote this little tool to seize the means of production!

Rather 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.

Usage

This is distributed as a Nix function, that can be used in your NixOS configuration, for example, like so:

{ pkgs, ... }:

let
  methadone = pkgs.callPackage ./path/to/methadone.nix { };
in
{
  environment.systemPackages = [
    (methadone.wrap {
      package = pkgs.claude-code;
      binary = "claude";
    })

    # Wrap as many as you like; they share one log
    (methadone.wrap {
      package = pkgs.github-copilot-cli;
      binary = "copilot";
    })

    # Methadone under its own name, which reports rather than wrapping
    methadone.stats
  ];
}

or, with Home-manager:

home-manager.users.YOU.home.packages = [
  (methadone.wrap {
    package = pkgs.github-copilot-cli;
    binary = "copilot";
  })
];

methadone.stats is optional and installs a methadone command that reports on what the log holds rather than standing in front of anything.

I don't use NixOS

I gotchu, bro. All you need is Babashka on your $PATH, plus a directory that takes precedence over the binary you want to wrap:

  1. Build the single-file script. The sources are a handful of namespaces, but what gets installed is one file, concatenated in dependency order:

    bb build
    
  2. Put it somewhere permanent:

    install -Dm755 methadone ~/.local/share/methadone/methadone
    
  3. Symlink it into a directory that comes earlier in your $PATH than the real binary, with that binary's name:

    ln -s ~/.local/share/methadone/methadone ~/.local/bin/claude
    
  4. Rinse and repeat for anything else you want to wrap: one symlink each, all pointing at the same script.

  5. Optionally, symlink it under its own name as well. Invoked as methadone, with no agent standing behind it, it reports on the log rather than wrapping anything:

    ln -s ~/.local/share/methadone/methadone ~/.local/bin/methadone
    

Methadone works out what to run from the name it was invoked as. It looks along $PATH for the next binary of that name which isn't itself, so the symlink shadows the real claude and Methadone finds it immediately behind. There's nothing to configure per binary.

If that's not what you want -- the real binary isn't on $PATH, or you want to wrap it under a different name -- set METHADONE_BINARY to its full path and Methadone will use that instead. That's how the Nix route works: the wrapper sets it for you.

Methodology

The 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.

Nothing expires; it fades

Methadone doesn't count usage inside a fixed window. Every session is weighted by its age instead, with an exponential decay in which $T$ is now and $W$ is the :window:

w(t) = e^\frac{-(T - t)}{W}

The decay of a session's weight with its age

A hard window is a cliff. Under a rolling week, last Monday's marathon stops counting this Monday and your friction drops for no reason you earned; a free pass on a schedule you could learn and time your work around. Decaying the weight removes the cliff: old sessions never stop counting, they just matter less.

:window is therefore a mean lifetime rather than a cut-off. The half-life follows from it, $w(t) = \frac{1}{2}$ at an age of $T - t = W \ln 2$, which a week's window puts at a tad under five days.

That gives two decayed totals: a count of launches $N$ and a time spent $D$. They are weighed differently, because a launch is an instant and takes the weight of its moment, whereas time spent is a span and so is integrated across the session. Writing $s_i$ and $e_i$ for the start and end of each, an open one ending at $T$ for the time being:

N = \sum_i w(s_i)
\qquad
D = \sum_i \int_{s_i}^{e_i} w(t) \, dt = W \sum_i (w(e_i) - w(s_i))

Integrating, rather than weighting the whole span at its start, discounts the older part of a long session against its newer and it has a pleasant consequence. A session you never close has $e_i = T$, so it contributes $W(1 - w(s_i))$: a quantity that approaches one window's worth and never reaches it, however long you leave the thing running. Its old end decays exactly as fast as its new end accrues. Leaving a session open forever is bounded, not infinite.

One number, two habits

The two measurements are traded against each other at a fixed rate and added together into a single score:

u = N + \frac{D}{E}

$E$ is the :session-equivalent. That rate and at its default of 30 minutes reads as a question in English: How long may a session run before it counts as another launch?

Both 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.

Because the cost of leaving a session open is only charged the next time you start one, Methadone says what it is weighing as it makes you wait:

Back for a top-up already, are we?
Lately: 6 launches, 9h 11m running, for a score of 25.

The 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.

Consider the following three habits to make that concrete; each a week's worth, at the point where the decay has settled:

Score
A light week5 sessions of 20 minutes8
A heavy week40 short sessions47
One long session a day7 sessions of 8 hours119

The last is what the duration term exists to catch. Under the original formula, which counted launches over a calendar day, it cost exactly the same as the lightest.

The curve

The score is fed through a logistic, of ceiling $L$, steepness $k$ and midpoint $u_0$:

f(u) = \frac{L}{1 + e^{-k (u - u_0)}}

The wait, against the usage score

Gentle while usage is ordinary, steep once it isn't, then levelling off rather than climbing forever.

That bound is deliberate. The obvious alternative -- keep doubling, as the original did -- reaches hours within a fortnight and a wait long enough to be worth circumventing buys no deterrence at all: the bypass is a single rm. A Methadone that's been deleted measures nothing.

A 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.

Turning the knobs

Two constants shape the curve and they do (almost) independent jobs.

:anchors is a pair of [score, seconds] opinions: what a light week and a heavy one ought to cost. The steepness and midpoint are solved for from them rather than written down, because 0.0645 and 84.04 are numbers nobody can sanity-check, whereas "ten seconds after a light week" is a judgement you can actually hold. :max-friction is the ceiling, $L$.

Inverting the logistic gives $\ln\frac{f}{L - f} = k(u - u_0)$, which is linear in the score, so a pair of anchors $(u_1, f_1)$ and $(u_2, f_2)$ is enough to fix both unknowns:

k = \frac{1}{u_2 - u_1}
    \left( \ln\frac{f_2}{L - f_2} - \ln\frac{f_1}{L - f_1} \right)
\qquad
u_0 = u_1 - \frac{1}{k} \ln\frac{f_1}{L - f_1}

Those logarithms are where the configuration guard comes from, too: their argument is positive and finite only for $0 < f < L$, so an anchor costing nothing, or costing the ceiling or more, leaves nothing to solve. See If you get it wrong.

Moving the ceiling barely disturbs the anchored region:

:max-frictionLight weekHeavy weekOne long session a day
10 minutes9 s2 min10 min
20 minutes9 s2 min18 min
30 minutes9 s2 min26 min

...and moving the anchors barely disturbs the ceiling's:

:anchorsLight weekHeavy weekOne long session a day
[[10 5] [50 60]]4 s49 s16 min
[[10 10] [50 120]]9 s2 min18 min
[[10 20] [50 240]]18 s3 min19 min

So the anchors set how the everyday feels and the ceiling sets what the worst case costs; you can tune either without upsetting the other. All four are settings, described under Configuration.

Configuration

Methadone runs on its defaults with no configuration at all. To change them, drop a methadone.edn in $XDG_CONFIG_HOME (usually ~/.config/methadone.edn):

{:window   [14 :days]
 :anchors  [[10 30] [50 300]]
 :log      "/home/you/.local/state/methadone/log.edn"}

Anything you leave out keeps its default, so a file need only name what you want to differ.

SettingDefaultWhat it is
:window[7 :days]The decay's mean lifetime
:retention[30 :days]How long a session is kept in full before it is reduced to a daily tally
:heartbeat[60 :seconds]How often a running session marks itself alive
:session-equivalent[30 :minutes]Runtime worth as much as one launch
:max-friction1200The longest possible wait, in seconds
:anchors[[10 10] [50 120]]Two [score seconds] opinions pinning the curve
:logXDG state pathWhere the log lives, if not where it usually would

Spans of history are given as [n unit], where the unit is one of :ms, :seconds, :minutes, :hours or :days; or as a bare number of milliseconds, if you prefer. Waits, being what you actually sit through, are always plain seconds.

:retention must be at least :window and Methadone refuses to start if it isn't: sessions the friction still counts would otherwise be reduced before it could count them and the wait would quietly fall for reasons you hadn't asked for. There is no need to set it generously beyond that, though: What is kept past it is the daily tally, which is kept for good regardless, so a longer retention buys detail nobody reads at the cost of a file rewritten on every heartbeat.

System-wide defaults

Methadone also reads a methadone.edn from each directory in $XDG_CONFIG_DIRS, after your own. Yours wins, so a NixOS module can install a policy at /etc/xdg/methadone.edn that you remain free to overrule.

If you get it wrong

Methadone refuses to start and says everything that is wrong in one go rather than one fault at a time:

Methadone cannot use its configuration:
  :windwo is not a setting Methadone has
  :retention must be a positive span: milliseconds, or [n unit] with unit one of days, hours, minutes, ms, seconds
  :anchors must rise, cost more than nothing and stay under :max-friction (1200 s)

Refusing outright is deliberate. The alternative -- shrugging and falling back to the defaults -- means a typo can quietly turn Methadone into something that isn't watching you at all, which is exactly the failure you would never notice. The anchor rule earns its keep here in particular: an anchor at or beyond :max-friction has no finite logarithm and the wait that falls out of the arithmetic is zero.

State

Methadone keeps a log at $XDG_STATE_HOME/methadone/log.edn; usually ~/.local/state/methadone/log.edn. Set :log if you would rather it lived elsewhere.

It holds two things. Sessions are recorded in full, one entry per launch per wrapped binary, with the moment it began and the moment it ended; these are what the friction is worked out from and they are kept for :retention. Once a session is older than that it isn't discarded but reduced: the detail goes and a tally of the day it began on -- how many launches, how long altogether -- is added to a history that is kept indefinitely.

The 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.

Reading it back

Invoked under its own name, with no agent in front of it, Methadone reports rather than wraps:

$ methadone
The last 7 days and what it costs you now:
  binary   launches    running   score    wait
  claude          9    11h 50m      21     20s
  copilot         2        45m       2      6s

Scored by week:
  earlier    59    46    35    36   now

The 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.

The trend is scored by week rather than decayed, so that its numbers can be compared with one another and with the rows above them. It draws on the history as well as on the sessions still held in full, so it reaches back further than :retention does.

Under Nix, methadone.stats installs it. Otherwise it is the symlink in step 5 above.

Isn't this trivial to bypass?

Yep. However, it's probably easier to just run it than trying to circumvent it. The idea is to provide enough friction to make you think twice before reaching for agentic AI tools and, hopefully, building a habit of re-engaging with your own work.

Deleting 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.

Ctrl+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.

Agent instructions

To try to further the point, I've included an example agent instruction set to reinforce the idea that you should be in control of your own work. Copy this wherever your agent of choice looks for instructions.

So... Did you use AI to write this?

I sure did...but maybe not in the way you are thinking. Almost all the application code is hand-written, under Claude's tutelage; part of Methadone's remit, despite my better judgement, is as a small project for me to learn Clojure. The only big chunks of code that are generated are the tests -- I hate writing tests! -- and the SVG plotter for this README. The friction model was brainstormed between me and the AI and some of the prose (here and in comments/docstrings) have been expounded at my request.

Maybe I like the misery

agentic-ai
marx
naur
nix

Contributors

Xophmeister

9 commits

Languages

Clojure

94.2%

Nix

5.8%