← back

your honeypot is a doorbell, mine is a POLYGRAPH

every threat report you have ever read describes a costume. "the actor used whoami, then net user, then dumped lsass with a renamed procdump." cool. that's T1033, T1087, T1003.001, three neat little ATT&CK codes, and every single one of them is a thing the operator CHOSE to do and can therefore choose to do differently tomorrow. TTPs are something you wear. you are profiling a hoodie.

and the sensor everyone bolts onto the front of this is a honeypot, which in 2026 mostly means a container that accepts root:123456, records nine commands, emits a JSON blob with a sha256 in it, and dies. a canary that chirps once and falls off the perch. congratulations, you have learned that the internet contains scanners. i had a whole pipeline of those and one day i sat down and worked out that in six weeks it had told me exactly one fact: port 22 exists. i wanted to know WHO WAS TYPING, or even if SOMEONE or SOMETHING was typing.

an IP is not a person

here is the actual problem with attribution as everybody practices it. the identifiers we key on are the ones the adversary rents. IP, ASN, JA3, user-agent, dropped filename, C2 domain. all leased, all rotatable, all cheap. you cluster on them and you are clustering rental agreements. two "different actors" who are one guy with a new VPS, and one "persistent actor" who is forty unrelated people behind the same residential proxy pool.

some people are going to say "oh, the TTPs are the hardest thing to change, it says it right there in the pyramid of pain!" or "the attacker can't just change their entire toolkit, that's painful!". in this day and age? not really. ask any obliterated model to replace things, change commands, swap execution models here and there, and you're a different person; at least, to MITRE ATT&CK.

so invert it. what does an operator emit that they are NOT choosing? not the commands. the MICROSECONDS BETWEEN THE COMMANDS. how long they hold a key. whether they backspace or route around. whether they hit tab. whether they read the output before they type again. none of that is tradecraft, all of it is motor cortex, and motor cortex does not rotate.

nocturnal psychosis, and a timestamp i'd been ignoring for years

i did not arrive at any of this cleverly. i got here at 4am, mid-build on DECNET, when it finally landed on me that i was doing the same thing as everybody else. more moving parts, nicer protocol twists, a genuinely lovely fake SMTP. practically identical in what it TOLD me. i was counting events and calling it intelligence. i recommend noticing this about your own project, it's horrible.

so i went looking for something fun to do instead, and the answer had been sitting in my own disk the whole time. i'd run cowrie for ages, and cowrie hands you asciinema recordings, and asciinema does not only record what was typed. it records WHEN. every input event carries its own timestamp, keystroke by keystroke, in a cast file i had been treating as a cute replay toy for years.

i had no idea what that was called. i just knew it was the only thing in the entire capture that the attacker was not choosing. so i went around describing "the gaps between the letters" like a lunatic to every human and machine that would listen, until one of them said: keystroke dynamics. biometry through neuromotor fingerprinting. turns out it's a real field, decades deep, with papers and equal error rates and everything.

i fell in love instantly and understood approximately none of it. still don't, fully. maybe 70% on a good day, and that's me being generous with meself. so i did the one thing that reliably works on me: build a framework for a subject you do not understand, because you cannot write a taxonomy for something without being forced to learn what its parts actually are. that's where BEHAVE comes from. the spec is downstream of ignorance, which is exactly why it's a spec and not a detector.

BEHAVE: a taxonomy, not a verdict

first thing i wrote was not a detector. it was a spec, BEHAVE, because i kept discovering that my "features" were fifteen ad-hoc floats with no agreement on what they meant. an observation is one measured value, for one named primitive, over one time window, with a confidence:

obs = Observation(
    primitive="motor.keystroke_cadence",
    value="bursty",
    confidence=0.65,
    window=Window(start_ts=1714000000.0, end_ts=1714003600.0),
    source="behave/shell-sensor/timing.py",
)
event_topic_for("motor.keystroke_cadence")
# -> "attacker.observation.shell.motor.keystroke_cadence"

that's it. 76 primitives in the shell registry across 9 categories, 41 in the text registry.

and here's the boundary that actually matters, because i butcher it whenever i explain this fast. the SENSOR reads your commands. it has to. there is no computing motor.shell_mastery.pipe_chaining_depth without parsing the pipeline, no cognitive.tool_vocabulary without knowing what got reached for, no spotting a landing_ritual without recognizing whoami/id/uname arriving in that order. content goes IN, and command complexity is a real analytic that needs the real command.

what never happens is content coming back OUT. the sensor parses, counts, buckets, hashes, and then emits deep, or narrow, or present, with a confidence and a window. the envelope has no field to smuggle the command through, and the schema won't validate a value the registry doesn't know about. the analytics get your shell history. the profile only ever gets adjectives.

and the reason for that split is not squeamishness, it's that observations are built to LEAVE THE BUILDING. BEHAVE is an interchange format. the point of a fixed envelope over named primitives is that two people who have never met can compare notes on the same operator without either of them mailing the other a pile of raw sessions. motor.error_correction = route_around, confidence 0.65, window attached: shareable. a cast file stuffed with somebody's typing, their victims' filenames and the guts of my own honeypot: not shareable, not ever.

which is also the part you should actually be afraid of, and i'd rather say it about my own work before somebody else says it for me. a portable behavioral fingerprint format that federates across organizations is a Palantir wet dream. that is precisely what this is. so the layer that TRAVELS carries adjectives, hashes, a confidence and a time window, holds no content, has no age or gender or nationality field, and is structurally incapable of concluding anything by itself. get the shareable layer wrong and what you've built is a very efficient way for strangers to point at someone. it could be you, or it could even be me.

and BEHAVE never says who you are. it emits observations and stops. it does not link sessions, it does not assign profiles, it does not name a group. that's a separate engine's job, on purpose, because the moment your measurement layer is allowed to have opinions you can no longer tell a bad reading from a bad conclusion.

the keyboard is a snitch

motor.* is the closest this gets to biometrics, and it is embarrassing how well it works, because typing is unconscious and unconscious things are consistent:

primitive what it splits
motor.keystroke_cadence steady metronome / bursty type-think-type / hunt_and_peck / machine
motor.motor_stability key hold and flight variance. steady, variable, or tremor
motor.error_correction immediate backspace under 1s / deferred after reading output / absent / route_around
motor.command_chunking fluent straight from memory / fragmented composing while typing
motor.shell_mastery.shortcut_usage ctrl+r, alt+., ctrl+a/e. heavy = years of muscle memory

error_correction: immediate means their brain is monitoring output faster than their eyes are reading it. that's an automatic loop and you cannot fake it upward. route_around means they hit up-arrow and rewrite instead of backspacing, which is a HABIT, formed years ago, invisible to its owner. shortcut_usage: heavy cannot be performed by someone who doesn't already have it, and someone who has it cannot easily perform not having it. deception has a floor and the floor is your own hands.

DECNET is the thing that actually measures this on live sessions, and the fun part is what it does with the numbers: keystroke rhythm becomes a digraph SimHash, and two identities get compared by hamming proximity between their hashes. DECNET itself keeps everything, obviously. it's a honeypot, recording what happens inside it is the entire job, cast files and all lol. what LEAVES it as a BEHAVE observation is a 64-bit smear of your RHYTHM. the idea being that the smear alone is enough to say "these two sessions, weeks apart, on two continents, same hands."

that's the idea. here is what my own machine actually returned. three real sessions, all me, all tonight-ish. A and B are me DEMOING at a talk, which is "hey check this out, pretty cool right!!" typing. C is me actually working. every number came out of the extractor:

primitive                                demo A           demo B           working C
motor.keystroke_cadence                  bursty     0.65  steady     0.70  bursty     0.65
motor.motor_stability                    variable   0.60  steady     0.70  variable   0.60
motor.error_correction                   immediate  0.65  immediate  0.65  immediate  0.65
motor.command_chunking                   fluent     0.65  fragmented 0.60  fragmented 0.60
motor.shell_mastery.shortcut_usage       none       0.65  none       0.40  none       0.65
motor.shell_mastery.pipe_chaining_depth  shallow    0.70  shallow    0.40  shallow    0.70
cognitive.inter_command_latency_class    typing_spd 0.80  llm_light  0.40  typing_spd 0.80
cognitive.inter_command_consistency      bimodal    0.75  variable   0.40  variable   0.75
cognitive.feedback_loop_engagement       fire_fgt   0.75  unknown    1.00  closed_loop 0.75
cognitive.error_resilience.retry_tactic    ...            ...             switch     0.65
temporal.session_duration                long       0.85  long       0.85  medium     0.85

feedback_loop_engagement flipped from fire_and_forget on stage to closed_loop at the desk, which is exactly right: demoing, i already know what the command prints; working, i actually read it. inter_command_consistency went bimodal on stage and variable at the desk, also right, because bimodal was me TALKING, my own speech pauses landing in the gaps. demo B went further and scored me llm_lightweight, a small model in a loop, at 0.40. my own agent detector looked at me and guessed ROBOT.

and error_correction: immediate came back in all three at identical confidence, along with shortcut_usage: none and pipe_chaining_depth: shallow. the habits held.

except one of those "habits" is a LIE, and it's mine. shortcut_usage: none in all three because the primitive hunts for ctrl+r, ctrl+a/e, alt+. and i run set -o vi. i would sooner DIE than press ctrl+a. so the instrument reads fifteen years of vi-mode muscle memory as a guy who doesn't know his shell, because the guy who wrote the detector encoded his own hands as the definition of expertise. that is exactly the failure mode you should assume is hiding in every behavioral primitive anybody sells you, including mine.

then the biometric, and here the honest answer is narrower than i want it to be:

demo A       a9cbb65835d49418
demo B       f040f7713ed19b58
working C    6bfaf1483fd1985e

A vs B  23/64      A vs C  20/64      B vs C  21/64
two coin-flipped hashes sit around 32/64

twenty to twenty-three bits apart, one guy, one keyboard, three sessions. under 32 so there's signal, nowhere near a match.

and the mechanism is right there in the code once you look: the SimHash tokens are digraph:flight_bucket, keyed by the actual character pair. two sessions only collide where they typed the same LETTER PAIRS. my fixture tests feed one fixed phrase at different speeds and behave perfectly, 0 bits for identical timing, separation for different cadence. three real sessions typing three different sets of commands barely share a digraph vocabulary at all, so the distance drifts toward noise no matter how identical my hands were.

that is the fixed-text versus free-text problem, and keystroke dynamics has known about it for decades: typing the same passphrase twice is a solved biometric, typing whatever you feel like is a research area. mine is currently a fixed-text fingerprint being asked to do free-text work. the fix is to score over the digraphs two sessions SHARE instead of SimHashing the union of both, and that's the next thing i build.

i also have no negative control. i have never run this against another human being, so i cannot tell you whether 20 bits is close or far, only that it isn't zero. i need to go get some other person to type at my honeypot, which, socially, i am dreading.

so: the human-versus-agent split is real and demonstrated. the person-versus-person fingerprint is a hypothesis with a hex string attached. i'd rather print that than have you find it for me.

thirty-seven things about a guy, in five minutes

all of the above is me quoting single rows at you. this is how DECNET actually hands it over. one session, five minutes of me typing, no agent, nothing special:

MOTOR                                     10 primitives
  input_modality                          typed                UNKNOWN   75%
  command_chunking                        fragmented           UNKNOWN   60%
  digraph_simhash                         6bfaf1483fd1985e     STABLE    95%
  error_correction                        immediate            UNKNOWN   65%
  keystroke_cadence                       bursty               UNKNOWN   65%
  motor_stability                         variable             UNKNOWN   60%
  paste_burst_rate                        none                 UNKNOWN   70%
  shell_mastery.pipe_chaining_depth       shallow              UNKNOWN   70%
  shell_mastery.shortcut_usage            none                 UNKNOWN   65%
  shell_mastery.tab_completion            occasional           UNKNOWN   55%

COGNITIVE                                 11 primitives
  feedback_loop_engagement                closed_loop          UNKNOWN   75%
  command_branch_diversity                adaptive_branching   UNKNOWN   80%
  inter_command_latency_class             typing_speed         UNKNOWN   80%
  cognitive_load                          medium               UNKNOWN   60%
  error_resilience.fallback_to_man        absent               UNKNOWN   65%
  error_resilience.frustration_typing     low                  UNKNOWN   60%
  error_resilience.retry_tactic           switch               UNKNOWN   65%
  exploration_style                       chaotic              UNKNOWN   60%
  inter_command_consistency               variable             UNKNOWN   75%
  planning_depth                          shallow              UNKNOWN   65%
  tool_vocabulary                         broad                UNKNOWN   70%

TEMPORAL                                  4 primitives
  escalation_pattern                      erratic              UNKNOWN   60%
  lifecycle_markers.exit_behavior         abrupt               UNKNOWN   45%
  lifecycle_markers.landing_ritual        absent               UNKNOWN   65%
  session_duration                        medium               UNKNOWN   85%

OPERATIONAL                               4 primitives
  cleanup_behavior                        none                 UNKNOWN   55%
  multi_actor_indicators                  solo                 UNKNOWN   55%
  objective                               recon                UNKNOWN   60%
  opsec_discipline                        learning             UNKNOWN   45%

ENVIRONMENTAL                             4 primitives
  keyboard_layout                         qwerty               UNKNOWN   55%
  numpad_usage                            not_detected         UNKNOWN   50%
  shell_type                              bash                 UNKNOWN   75%
  terminal_multiplexer                    none                 UNKNOWN   55%

EMOTIONAL VALENCE                         4 primitives
  arousal                                 high_agitated        UNKNOWN   50%
  frustration_venting                     detected             UNKNOWN   40%
  stress_response                         none                 UNKNOWN   50%
  valence                                 neutral              UNKNOWN   50%

five minutes. thirty-seven statements about a person, from an ssh session, with no content in any of them.

and then there's that last category, which i wrote and still find rude. arousal: high_agitated. frustration_venting: detected. it is not wrong!!! i was tired and swearing at my own tooling at the time!!! but sit with the fact that a terminal session leaked my MOOD to a machine, at 40 to 50 percent confidence, as a side effect of measuring nothing but when keys went down. opsec_discipline: learning is the one that actually hurt, though. that's the profiler's polite way of saying "this guy is getting better at hiding, from a low base."

notice the stability column too: UNKNOWN on nearly everything, because one session is not enough history to call any of these a stable trait. that column is the tool being more careful than its author. a profile is a claim about a person built from a handful of five-minute windows, and it says so.

is there even a person in there

now the 2026 problem, which is my favorite section of the whole taxonomy. half the hands-on-keyboard activity people breathlessly attribute to threat groups is a script, and an increasing slice of it is an agent, and the industry's answer to "was that a human" is vibes. so it's a primitive:

cognitive.inter_command_latency_class
  instant           <200ms      scripted
  typing_speed      200ms-2s
  deliberate        ~2s         human thinking
  llm_lightweight   2-8s        small model in a loop
  llm_heavyweight   8-30s       reasoning-class agent
  long              >30s        human supervising an LLM

cognitive.inter_command_consistency
  metronomic   dispersion near zero      pure LLM
  variable     human
  bimodal      LLM-paced bursts + human thinking gaps   <- assisted human

bimodal is the one that makes me cackle. it catches the guy who is pasting an agent's suggestions and then stopping to think, which is what basically everyone doing this work actually looks like now, INCLUDING US. and cognitive.feedback_loop_engagement cuts across the whole axis: does the pause before the next command grow when the previous command printed 400 lines? closed_loop means something read it. fire_and_forget means nothing did. a script never once looks at what it just did, and that indifference is measurable.

then cognitive.error_resilience.frustration_typing. speed and error rate spiking immediately after a command fails. scripts do not get mad. it is a genuinely great human detector and it is, essentially, a polygraph you never agreed to sit for, reading a cortisol spike through a TCP socket lol :)

and this part i can actually show you working, which is more than i can say for my prettier claims. five labeled sessions out of my prototype fixtures. one human, that's me, and four agent runs:

session       keys  pastes  modality  latency_class    consistency  feedback_loop    error_corr  fingerprint
human          203       9  typed     deliberate       variable     fire_and_forget  immediate   e99ef561b9f518be
agent 1         11      10  pasted    llm_lightweight  metronomic   fire_and_forget  absent      (none)
agent 2          1      10  pasted    llm_heavyweight  metronomic   fire_and_forget  absent      (none)
agent 3          1      10  pasted    llm_lightweight  metronomic   fire_and_forget  absent      (none)
supervised       1      10  pasted    long             metronomic   closed_loop      absent      (none)

clean on every axis that matters. metronomic on 4/4 agents and variable on the human. error_correction: absent on 4/4, because a paste never makes a typo and therefore never corrects one. the latency class sorts the agents by weight class without being told which was which. and the supervised run is the only agent that comes back closed_loop and long: something was reading the output and thinking before it pasted again.

then look at the fingerprint column, because it's the funniest thing in this post. the agents don't have one. an agent that pastes produces no single-char keystrokes, so there are no digraphs, so the biometric declines to emit at all. the neuromotor fingerprint's SILENCE is itself a robot detector.

profiles are then just weighted patterns over these, kept in a separate layer that can be wrong out loud:

profile: ransomware_affiliate
dominant_observations:
  - {primitive: motor.keystroke_cadence,     value_in: [steady],         weight: 0.5}
  - {primitive: cognitive.exploration_style, value_in: [methodical],     weight: 0.7}
  - {primitive: temporal.session_duration,   value_in: [long, marathon], weight: 0.5}
necessary_observations:
  - {primitive: operational.objective,       value_in: [destructive]}
incompatible_observations:
  - {primitive: identity.engagement.authorized, matches_session: true}   # not red team
min_confidence: 0.65

the engine emits attribution.profile.candidate continuously and attribution.profile.current as an explicitly-labeled VIEW, not a fact. i cannot stress enough how much of the design is just making sure the thing can be told it's wrong.

my lover got caught by an idiom

a few weeks in i hit the ceiling: keystroke dynamics is a gorgeous signal and it is also THIN. on its own it needs a live session, on my box, with a human at the far end. that is a very narrow door to build an entire field of profiling behind.

so i thought about Ted Kaczynski, my one and only true love, who spent almost two decades unidentified and then got taken down by PROSE. no fingerprints, no informant, no cell tower. his brother read the manifesto in the newspaper and recognized the PHRASING. then forensic linguists set it beside letters and essays the man had written years earlier and found the same sentence architecture, the same tics, including his habit of using an older form of the idiom, "you can't eat your cake and have it too." that comparison is what went into the search warrant affidavit.

think about how insane that is. a guy who got hunted for seventeen years without leaving a usable physical trace was undone by his own FUNCTION WORDS. the small boring connective tissue nobody chooses on purpose. and the guy whose notes i take got identified by precisely the science i was, at that moment, sitting down to go build, which is either poetry or a warning, and i've decided it's both :)

so BEHAVE split in three. core for the envelope, BEHAVE-SHELL for hands, BEHAVE-TEXT for everything a person emits while they think they're only talking.

the text is a different animal entirely

shell dynamics need a session. you have to be in my box, typing, live. that limits the whole approach to people who came to me.

text doesn't need that. this is BEHAVE-TEXT and its implementation, EYENET, and it is a much bigger and much worse instrument. same envelope, different substrate: telegram, chat rooms, forums. collectors multiplex dialogs onto a NATS bus, the engine folds each observation into a versioned profile snapshot, and role recipes score it. and the primitives are mean:

  • stylometric.pos_ngram_signature. a SimHash over part-of-speech bigrams. you can change every single word you write and keep the same grammatical skeleton, because syntax is deeper than vocabulary and you did not choose yours.
  • stylometric.distinctive_vocabulary_signature. TF-IDF-weighted rare words, hashed. the words you use that nobody else in the room uses.
  • lexical.optional_grammar_signature. this one is a knife. spanish gives you free choices you don't experience as choices: he comido versus comí, leísmo, subjunctive rate. the preference vector over those choice points is a near-clean spain-versus-latam discriminator, and it was installed in you before you were seven.
  • lexical.code_switching_rate and code_switching_matrix_language. which language is the grammatical scaffold, which ones show up as inserts, how often you flip. es-CL writes differently from es-AR and neither of them knows it.
  • stylometric.capitalization_habit, punctuation_style, typo_signature. yes, your typos are a fingerprint. THE SAME ONES. every time.

want to see how loud this signal is? go read any chat leak a newspaper has ever reprinted. earlier this year a chilean cyber criminal case got its operators' own posts quoted verbatim, and a journalist carefully typed (sic) next to los kiero. that sic is stylometric.typo_signature. k-for-qu is not an error, it's a HABIT, stable across years and platforms and unbothered by which handle you're wearing. a few words earlier, breacheamos, an english verb stem wearing spanish first-person-plural morphology, is lexical.code_switching_rate waving.

the press printed a fingerprint and labeled it a typo, which is more or less how the entire field treats this class of signal. mere noise.

attacker.* versus actor.*

so here's a detail that looks like naming pedantry and is actually the ethical position of both tools. the shell registry publishes to attacker.observation.shell.*. the text registry publishes to actor.observation.text.*.

different word, on purpose. everybody in a shell session on my infrastructure got there by kicking a door. they opted in, loudly. a chat room contains admins, buyers, sellers, bots, and lurkers who did nothing but sit there, and if my subject taxonomy calls all of them attackers then i have prejudged every person in the room by their proximity to one.

and there is no version of this capability that only points at the guilty. keystroke dynamics at least need a hostile session to exist. stylometry needs nothing. no breach, no login, no consent, no suspect, just prose, and everyone alive has been producing prose in public for twenty years. the POS-bigram hash that welds two personas together inside a criminal channel is the same hash that welds a pseudonymous blogger to their employer, and it does not know or care which of those two i asked it for.

that's why the envelope has nowhere to put raw text. the extractor reads the messages, of course it does, that's the job. same leave-rule as the shell side.

two boxes, different rules

EYENET keeps the messages. on disk, with the attachments, under access control, indefinitely. not for the profiling, which is done and hashed long before that, but because EYENET is an investigation tool and an investigation that cannot produce the original is worth NOTHING. an operator has to be able to re-derive a hash, put the raw message next to the derived claim, and hand somebody an evidentiary copy that survives being checked by a hostile party. you do not get evidence-grade without the evidence.

which is why the document classifier that tiers all that evidence is deterministic on purpose: regex and PII floors combined by a monotone MAX, tiers promotable and never demotable, so re-running it on the same bytes with the same ruleset version gives the same verdict and the verdict is therefore defensible by somebody who is not me. anything it cannot read or parse settles at the HIGHEST tier, because over-classifying annoys an operator and under-classifying leaks, and only one of those is a catastrophe. and there is a local LLM in that pipeline whose entire authority is raising a flag: it can say "i think this is more sensitive than you scored it", it cannot move the tier, and its suggested demotions are never acted on automatically. same rule as the profile layer, in a completely different subsystem, arrived at twice. the model advises. it does not get a VOTE.

so the rule was never "we store nothing", which is what every vendor says and none of them mean. it's a split with two different sets of rules: the profile layer is hash-only and banned from concluding anything, and the evidence layer keeps the original because the alternative is an accusation with a vibe behind it. two boxes. pretending they're one box, in either direction, is the lie. and it's why there is no age primitive, no gender primitive, no nationality primitive: people claim those in chat constantly, saying is not being, and a claim is content. self-asserted anything lands in user-owned identity.label.* that the sensors may never write into. the sensors get to measure how you type. they don't get to believe you.

the constraint isn't a disclaimer stapled to a readme, it's the shape of the thing. it's also why every worked example above is synthetic stub output or something a newspaper already printed, and the corpus i actually have sits on a disk and stays there.

two substrates, one pair of hands

and then you get the payoff, which is that these are the same person.

an operator who types with route_around correction and heavy shortcut usage in a shell, and writes with a particular POS-bigram hash and a chilean matrix language in a chat room, is one identity observed through two windows. the linker proposes an edge, with a confidence, and a human accepts or rejects it. no IP was involved at any point. no VPN helped. no infrastructure rotation touched it.

and i got a live demonstration of why that matters, at my own expense, the first time i tested this properly. i ran a session against my own honeypot from a box with two internal addresses, changed address mid-stream without thinking about it, and DECNET filed me as TWO SEPARATE INTRUDERS. one of them got a full profile. the other got a single event and nothing else, because you cannot fingerprint a guy who only said hello.

which is exactly the failure i have spent this entire post mocking other people for. i wrote four hundred words about clustering rental agreements and then my own identity layer keyed on an address and split one operator into two people, in a lab, with the operator sitting right there watching it happen. the linker exists for precisely this. welding those two rows back into one identity on rhythm alone, without either address, is the next result i owe you.

your honeypot logged a source address and a password guess. i have a graph where the nodes are HANDS.

where this goes, and what kills it

the near future of this is not "identify the human." it's identify the HARNESS. within a couple of years most hands-on-keyboard activity in an intrusion won't be hands, and the interesting fingerprint stops being motor cortex and starts being which model, which scaffold, which retry policy. inter_command_latency_class is already groping at it by accident: a 2-8 second cadence and an 8-30 second cadence are different products. a fixed retry-on-failure with an identical command is a for loop, and modify-then-retry with the argument order changed is a sampler at temperature. model families are going to have cadence signatures the way TLS stacks have JA3, and nobody is collecting them yet.

but the operator does not vanish, they get RARER and therefore louder. every agent has a supervisor, and the supervisor's only remaining behavior is choosing when to intervene. that's what bimodal is: the machine's tempo with human decisions punched through it, and each of those holes is a sample of somebody's actual judgment. fewer data points, higher quality. the interventions are where the person is.

and this is where i think everyone is going to get it wrong, so: once cadence profiling is table stakes, tradecraft adapts. you type through a normalizer, you paste everything, you jitter your own timing. fine. that only burns the primitives you can route around. what survives is the stuff that is still a CHOICE made under pressure: what you reach for when a command fails, whether you read output before continuing, how deep you nest a pipeline when nobody's watching, what you decide is worth pasting in the first place. temperament doesn't have a knob. panic is a reconoissance tool.

and there's a nastier consequence hiding in that, which took me a while to see. to imitate a cadence you need a cadence to imitate, which means a table of per-digraph flight times. that table is not a description of somebody. it IS that somebody. whoever holds one can wear those hands, and unlike a password you cannot rotate the way you type. so the moment this kind of profiling is worth doing, the timing corpora become the most sensitive artifact in the whole pipeline, more sensitive than the sessions they came from. findings and methods travel. the tables never do, from anybody, to anybody, for any reason.

the part with no answer yet is that all of this scales down to one person, which means it scales out to every person, and the field has approximately zero norms about that. i wrote the taxonomy with holes cut in it on purpose. that is not a solution, it's one guy's guess at where the line goes, and i'd rather argue about it now than after somebody ships this with a marketing page.

one last thing. nobody helped me build any of this. DECNET is a bit over 1500 commits since april and every one of them is mine. EYENET came after it, is the better system by a distance, and is the one i'm actually proud of. no team, no design partner, no grant, just months of nights and a graveyard of dead branches.

i'm not saying that to be impressive, i'm saying it because when somebody eventually explains to you that behavioral attribution requires a platform contract, a seven-figure pilot and an onboarding workshop, you should know that one tired guy in chile got this far with a honeypot, a spec, and a timestamp that had been sitting in his own log files for two years while he ignored it.

and yes, i built both of these with AI in the loop, constantly. i'm hardly going to pretend otherwise in a post containing a detector for exactly that, which read two of my own sessions as bimodal and one as llm_lightweight. i am the assisted human. i'm in my own dataset.

what i did NOT do is vibe code. vibe coding is shipping a system you cannot read, did not design and could not defend if somebody asked you a hard question about line 400, and it is a DISGRACE. the taxonomy is mine, primitive by primitive, all 117 of them, and i can tell you why each one exists and what it fails at. the fail-closed rules are mine. the decision that the measurement layer never concludes anything is mine. the split between the shareable layer and the evidence layer is mine. the model typed plenty of it and decided none of it, which is the same rule i put in the classifier and the same rule i'm putting here: it advises, it does not get a VOTE.

check DECNET and BEHAVE out.

anyway, my sensors are ugly, my SimHashes are 64 bits of pure chaos, and my confidence intervals are held together with hope and property-based tests. but the next time somebody rotates every single indicator i own and reconnects on a fresh box from a fresh country, the thing is going to look at how long they hold the E key and say

IF ONLY YOU KNEW ABOUT MY BRAIN AND EVERYTHING THAT GOES THROUGH IT!!!!!!!!!!!!1