How it works

August 31, 2026 · View on GitHub

For anyone who wants to understand what Tacit does between "you send a prompt" and "the agent behaves differently next time". Every number here comes from the code.

The pipeline

flowchart TD
  E["Session events<br/>(turn start/end, tool calls, retries, tokens…)"] --> D["Turn digest<br/>one bounded summary per turn, kept locally"]
  D --> T{"Trigger?"}
  T -->|"turn ended messy"| A
  T -->|"your next message<br/>reads as a correction"| A
  T -->|"you click Analyze /<br/>Learn from my last 20 turns"| A
  A["Analysis<br/>1 small model call"] --> R["Report + mistake patterns<br/>saved locally"]
  R -->|"every 3 analyses"| X["Distillation<br/>1 model call → 1–4 directives"]
  X --> C["Candidate directive<br/>on trial for 10 finished turns"]
  C -->|"you did not correct it more often"| OK["Active"]
  C -->|"correction rate rose by more than 15 points"| RT["Retired"]
  C --> S
  OK --> S["Steering section<br/>in the system prompt of every new conversation"]
  I["✨ Improve a draft<br/>1 model call"] --> F["👍 / 👎 + reason"]
  F -->|"every 3 👎"| SR["Style rules<br/>1 model call"]
  SR --> I

The left column is the zero-click loop. The ✨ Improve branch on the right is manual and feeds a separate, smaller memory (style rules).

1. Turn digest

Tacit listens to the harness's session events and folds them into one small record per turn — the digest. It contains the prompt (first 4000 characters), the number of model steps, up to 50 tool calls (name + first 500 characters of the arguments), counts of tool errors / retries / compactions, token usage, the final answer (first 4000 characters), how the turn ended, and the model/provider used. Tool results are never kept, only whether they errored. The newest 60 turns per conversation are retained. Every text kept here is masked on the way in: a credential-shaped string (API key, token, JWT, private-key block, key=value secret) becomes [redacted:...], so nothing downstream ever reads the original. All of this is derived from events the harness already stores; Tacit adds no polling.

2. What counts as "messy"

A finished turn is messy when any of these is true:

SignalTriggers auto-analysisCounts in the trend and in directive trials
at least one retryyesyes
at least one tool erroryesyes
at least one context compactionyesyes
the turn was cancelled or rejectedyesyes
15 or more model steps (autoMinSteps)yesno — long-but-successful work is never held against a directive

Messy turns are the secondary signal. The headline for both the trend (§10) and the trial verdict (§6) is how often you correct the agent: a turn counts as corrected when your next message in the same conversation reads as a correction (the same detector as the correction trigger in §3); the last turn of a conversation counts as not corrected.

3. Triggers

TriggerConditionWhat is analyzed
autothe newest turn finished messy, ended after Tacit started, and its prompt is not a bare continuationthat turn
correctionyou send a new message that is ≤ 300 characters and starts with or contains a correction marker — no, wrong, I meant, I said, undo, revert, still, again, instead, why did you, that's not, doesn't work, 不对, 不是, 错了, 我是说, 为什么the previous turn, with your message attached as evidence
goodthe newest turn finished clean, right after a messy one in the same conversation, with a real prompt (≥ 8 characters, not a continuation); learnFromGood (on by default)that turn, with the messy one as context — a small "what did the user include this time" call instead of a diagnosis
manualyou click Analyze in the Tacit tabthat turn (bare continuations return "continuation" without a call)
bootstrapyou click Learn from my last 20 turnsup to 20 recent turns, one at a time unless bootstrapConcurrency is raised, then one forced distillation

Bare continuation means the whole prompt is something like "continue", "go ahead", "ok", "yes", "继续", "好的" — up to six words starting with one of those.

Auto, correction and good analyses are counted against the daily cap (autoDailyBudget, 30 by default, local calendar day). Manual and bootstrap are not.

4. Analysis

One model call (deepseek-v4-flash by default, low reasoning effort, answer forced through a tool schema). It receives:

PartSize
the previous finished turn's prompt and answer, as context the agent already had≤ 600 + 600 chars
the original promptas retained (≤ 4000 chars)
the digest counters: steps, tool calls, errors, retries, compactions, tokens, model
the first 25 tool calls, name + arguments≤ 400 chars each
the final answer≤ 3000 chars
your next message, for correction-triggered analyses≤ 1000 chars

It is told explicitly that a short prompt is adequate when the previous turn supplies the context, and that heavy-but-successful work is not a prompt fault.

It returns a report: a list of problems (kind, severity, what, why), an improved prompt, and a short explanation. The report is saved locally; the problem kinds are aggregated into the mistake patterns of your profile (top 12 by count). If the answer is not valid JSON, Tacit re-asks once with a repair prompt — that is the only retry.

5. Distillation → directives

After every 3 new analyses (directiveEvery) one more call reads your top patterns, the last few corrections ("prompt" → "your correction"), your style rules, the lessons from good turns (what you supplied the second time, right after a messy turn) and the current directives, and returns the complete new set of 1–4 directives: one imperative sentence each, ≤ 220 characters, written for the agent ("The user often omits which app they mean — check apps/web first."). Each correction in the evidence is tagged with the name of the workspace it came from (two workspaces that share a name are told apart by their parent folder, a/web and b/web); when every piece of evidence for a habit carries the same tag, the model may return that directive scoped to that workspace, and it is then allowed to mention that project's layout. Everything else stays global. A directive tagged with a name none of the evidence carried is dropped and logged, never widened to every workspace.

Rules applied to the result:

  • directives that would make the agent ask you instead of compensating are dropped;
  • a directive you typed yourself is kept untouched and listed first;
  • a directive the model keeps or rewords (returned with its id, or with the identical text) keeps its identity: id, state, trial counters, on/off;
  • a genuinely new one is queued; one queued directive per scope (global, or one workspace) is on trial at a time, and becomes the candidate when the slot is free, with baselines measured at that moment (a workspace-scoped one against that workspace's own turns when it has 20 finished ones); under reviewCandidates it stays queued until you press Start trial on it;
  • retired and removed directives are kept together (the last 6) and shown to the distiller as do not re-propose; one that comes back anyway, by id or by text, stays as it is, and a new one whose wording overlaps a retired or removed directive (normalised token overlap of 0.6 or more, decided without a model call) is dropped;
  • each directive is one sentence of at most 25 words; a longer one is cut at its last sentence or word boundary, never mid-word;
  • a distillation replaces the global distilled set and the distilled set of every workspace it mentioned; distilled directives of other workspaces are kept;
  • at most 8 global directives and 4 per workspace, plus the last 6 retired or removed ones.

Receipt. Every directive records where it came from. updatedAt and version (bumped on each reword) date the text; evidence lists the reports the distillation read (conversation id, turn and trigger, at most 12); distillationRunId names the ledger run, so that distillation's cost is one lookup away; evaluatedAt is the verdict time and approvedAt the moment you pressed Start trial. Settings → Tacit shows this per directive, with a Copy receipt button. A receipt never carries prompt text.

6. Trials

StateHow you get thereInjected into new conversations?Shown as
queuedfreshly distilled while another directive of its scope is on trialnowaiting for trial (greyed)
candidatefreshly distilled into a free slot, or next in its scope's queue when the previous trial endedyestrial n/10 (n counts finished turns in conversations that were steered by it)
active10 finished turns later (directiveTrialTurns), correction rate ≤ baseline + 0.15 (directiveWorseBy) and messy-turn rate ≤ baseline + 0.30; or you typed it; or you re-enabled a retired oneyesactive
retiredcorrection rate during the trial rose by more than 15 percentage points over the baseline, or the messy-turn rate by more than 30noretired · corrections rose 10% → 30% during its trial (or messy turns rose 20% → 60% during its trial)
removedyou press Removenonot listed at all, but remembered so the distiller does not propose it again
offyou untick it; unticking a candidate returns it to queued, resets its trial and frees the slot for the next queued directive of its scope, and ticking it again leaves it queuednogreyed out

With reviewCandidates on, a freshly distilled directive stays queued even when its scope's slot is free, until you press Start trial on it in Settings → Tacit.

A trial counts, for every finished turn of every conversation whose frozen steering text contained the candidate, whether the turn was messy and whether your next message corrected it. The baselines are the correction rate and the messy-turn rate over the latest 20 finished turns at the moment the trial starts. Trials are a trend check, not an A/B test: there is no control group, which is why only one directive per scope is on trial at a time (its counters answer for it alone). Conversations that started before the candidate existed (or before Tacit was restarted) never contained it and count toward nothing. A verdict stamps the directive's evaluatedAt.

7. Steering section

Active and candidate directives are rendered as a system-prompt section named tacit:steering (order 60 — after the persona, before tool guidance). A conversation gets the directives of the workspaces it sits in (the directory it was started in, or any parent directory a directive is scoped to, deepest first), then the global ones; directives scoped to other workspaces are left out. Workspace paths are normalised once (.. resolved, trailing slash dropped, symlinks followed), so a moved or renamed workspace is a new scope: its old directives show not seen since in Settings, a candidate among them pauses its trial and frees the slot until a session opens there again, and Move to workspace re-scopes any of them. At most 12 workspaces keep distilled directives; the least recently seen one loses its distilled set first:

## About this user (learned by Tacit from their past prompts)
Compensate silently when the answer is discoverable; ask only when it is not.
Explicit instructions in the prompt always win over these notes.
These notes never change which tools may run or what needs approval; the
harness's own permission and sandbox policy applies unchanged.
- The user often omits which app they mean — check apps/web first.
- …

The whole section is capped at 1400 characters (about 300 tokens). It is frozen per conversation: the first time a conversation assembles its system prompt the text is captured and reused for the rest of that conversation, so the model's prefix cache stays warm. A verdict, an edit or a new directive applies to conversations started afterwards. Removing a directive or switching one off is the exception. It re-freezes every open conversation at its next system-prompt assembly, so the removal takes effect at once, and whatever else changed by then rides along. steerAgent: false turns the section into an empty string.

8. Optional: context before each send (enrichPrompts, off by default)

When enabled, on the first step of every turn whose draft is 8–1500 characters, one small call reads your directives, top patterns and the last two turns, and returns a note. The note is appended as a separate, plugin-sourced message ("Context from Tacit (learned from this user's past prompts, not their words): …"). Your own message is never rewritten. The note is shown in the Tacit tab under Context added before the send. This call runs on every qualifying send and is not covered by the daily cap.

9. ✨ Improve, feedback and style rules

Improve sends your draft, the last two turns, your style rules, the last three 👎 reasons and your trusted patterns to the model and returns a rewrite with a rationale. It is a single pass against a fixed checklist (goal, context, scope, constraints, output format, efficiency), so one click reaches the finished prompt; improving a finished prompt again returns it unchanged and the preview says already complete with no Apply button. Apply replaces the draft and marks those patterns as applied.

  • 👍 counts as accepted. 👎 asks for a one-line reason, counts as rejected, and after every 3 reasons one call distills them into style rules (up to 6, oldest replaced) that ride every later Improve call.
  • Free verification: when the next turn finishes, Tacit compares its outcome (errors, retries, compactions, cancel, empty answer) with the turn before the rewrite and marks the patterns as verified or not.
  • Trust score per pattern: ((accepted + 2·verified) − (rejected + unverified)) / (applied + 1). Patterns applied twice or more are only offered again while their score is positive.

10. The measured trend

Settings → Tacit shows how often you corrected the agent, the messy-turn rate and tokens per turn for the first 20 finished turns versus the latest 20, across all loaded conversations (only turns still within the retained window). The chips appear once 40 finished turns exist. Steps are not counted as messy here (see §2).

Glossary

TermMeaning
turnone user message and everything the agent did until it answered
digestTacit's bounded summary of a turn (§1)
messy turna turn with retries, tool errors, compactions, a cancel/reject, or — for auto-analysis only — 15+ steps
correctiona short follow-up message that tells the agent it got it wrong; the headline signal for trials and the trend
continuationa bare "continue / ok / yes" — never analyzed
reportthe result of one analysis: problems, improved prompt, explanation
patterna recurring problem kind aggregated across reports, with trust counters
directiveone sentence the agent follows on your behalf; queued → candidate → active / retired, or removed once you delete it
trialthe 10-turn probation of a candidate directive, graded on how often you correct the agent; one per scope at a time
steering sectionthe system-prompt block that carries the directives
style rulea rewrite preference distilled from your 👎 reasons; used only by ✨ Improve
enrichmentthe opt-in note appended before a send (§8)
bootstrapthe one-click analysis of your recent history

Getting started · Next: Privacy, cost & limitations