taskdetect

package
v0.35.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package taskdetect notices when work the user just did should become a durable capability, and proposes the task that would do it.

Two independent kinds of evidence feed one proposal system:

a STRONG single turn   this piece of work is plainly repeatable
REPEATED weak signals  the same capability keeps being asked for

The second is the one that matters. Anyone can suggest cron. Recognising "you have asked me to keep provider catalogs current four times across three sessions, shall I just keep them current" is memcode learning a capability from working with someone, and it is a different message with a different weight.

The detector's output is a PROPOSED TASK, never a boolean. Interrupting somebody to ask "want to automate that?" and then making them answer a configuration questionnaire is worse than not asking: if there is enough confidence to interrupt, there is enough to propose cadence, authority, verification and runtime, and let them accept it whole.

Index

Constants

View Source
const (
	ActionCreate    = "create"
	ActionCustomize = "customize"
	ActionNotNow    = "not_now"
	ActionNever     = "never"
)

The four actions. Deliberately not Yes/No: the difference between "not right now" and "never this kind" is the difference between a system that learns and one that nags.

Variables

This section is empty.

Functions

func DBPath

func DBPath() (string, error)

DBPath keeps signals alongside the rest of memcode's per-machine state.

func Ready

func Ready(c Cluster) bool

Ready reports whether a cluster is worth offering, by EITHER path.

Both are checked here because a prospective offer is made in the middle of a session, where nothing may interrupt — so it waits with the historical ones, and would be silently lost if this only knew about accumulated evidence.

func WouldOfferOnFirstContact

func WouldOfferOnFirstContact(p Proposal) bool

WouldOfferOnFirstContact reports whether a proposal clears the first-contact bar, without touching any store. Exported so the detector evaluation measures the REAL rule rather than a paraphrase of it that can drift.

Types

type Classifier

type Classifier interface {
	Classify(ctx context.Context, in Turn) (Proposal, bool, error)
}

Classifier turns a finished turn into a proposal. Injectable: the real one costs a model call, and the pipeline around it has to be testable without one.

type Cluster

type Cluster struct {
	FamilyKey string
	Family    string
	Project   string
	// Weight is confidence summed with age decay. Sessions counts DISTINCT
	// sessions, which is the part that matters: three signals in one
	// conversation is one person repeating themselves, not a pattern.
	Weight   float64
	Sessions int
	Signals  int
	// Latest is the most recent proposal, used for the offer — the newest
	// phrasing of the capability is the one to show back.
	Latest   Proposal
	Evidence []string
	LastSeen time.Time
}

Cluster is the accumulated evidence for one capability.

func (Cluster) Capability

func (c Cluster) Capability() Proposal

Capability returns the cluster's proposal named for the CAPABILITY rather than for whichever instance happened to be seen last.

A cluster is the recognised standing job; accepting it should produce "provider-catalog-maintenance", not "check-fireworks-catalog" just because Fireworks was the most recent thing asked about. The instance name is right for a single-turn offer and wrong here.

type Decision

type Decision struct {
	Kind     Kind
	Proposal Proposal
	// Sessions and Occasions describe the evidence behind a repeated offer.
	Sessions  int
	Occasions int
	// Why explains a decision NOT to offer, for the debug surface. Silence that
	// cannot be explained is indistinguishable from a broken detector.
	Why string
}

Decision is the outcome of evaluating a turn.

func ClusterDecision

func ClusterDecision(c Cluster) Decision

ClusterDecision renders a waiting cluster as the offer it should become.

Accumulated evidence wins when it exists: "you have asked me this three times" is a stronger and more personal claim than "this kind of thing recurs", and having earned it, memcode should say it.

type Detector

type Detector struct {
	Store      *Store
	Classifier Classifier
}

Detector evaluates finished turns and decides whether to offer anything.

func (*Detector) Evaluate

func (d *Detector) Evaluate(ctx context.Context, in Turn, now time.Time) (Decision, error)

Evaluate classifies a turn, records what it learned, and reports whether to make an offer.

Recording happens even when no offer follows: today's weak signal is what makes next month's recognition possible, and a detector that only remembers what it already acted on can never learn anything.

type Effect

type Effect string

Effect says what a run of this work does to the repository.

const (
	// EffectNone reads and reports; it changes nothing.
	EffectNone Effect = "none"
	// EffectPossible may change code, depending on what it finds. The common
	// case for maintenance: usually nothing to do, sometimes a real diff.
	EffectPossible Effect = "possible"
	// EffectExpected changes code essentially every run.
	EffectExpected Effect = "expected"
)

type Gap

type Gap struct {
	Field    string
	Question string
	Why      string
}

Gap is something that must be settled before a task can safely run unattended, with the question to ask.

type Kind

type Kind string

Kind is which evidence path produced an offer. They read differently to the user because they mean different things.

const (
	// KindNone: nothing to offer.
	KindNone Kind = ""
	// KindProspective: the work will be needed again for a reason that exists in
	// the world, said on FIRST contact. The primary path — memcode understanding
	// the work rather than waiting to watch someone repeat it.
	KindProspective Kind = "prospective"
	// KindRepeated: recurrence learned from behaviour instead of inferred. The
	// fallback, and the right answer when the work has no inherent cadence but
	// this particular person keeps wanting it.
	KindRepeated Kind = "repeated"
	// KindExplicit: the user asked for it to be automated. No inference needed.
	KindExplicit Kind = "explicit"
)

type Offer

type Offer struct {
	Headline string
	Detail   string
	Options  []Option
}

Offer is the message and choices shown to the user.

func Message

func Message(d Decision) Offer

Message renders the offer for a decision.

The two paths read differently ON PURPOSE. A single-turn offer is a suggestion about the work just done. A repeated offer is memcode saying it noticed a habit and can take it over — which is a materially bigger claim, and flattening both into one generic automation prompt throws away the only part that is interesting.

type Option

type Option struct {
	Key         string
	Label       string
	Description string
}

Option is one action.

type Proposal

type Proposal struct {
	// Family is the SEMANTIC identity of the capability, normalized. It is what
	// lets three differently worded requests accumulate into one recognised
	// thing, so it names a capability rather than an instance:
	// "provider-model-catalog-maintenance", not "update-anthropic-models".
	Family string `json:"task_family"`
	// Operation and Target are the parts Family is built from, kept separately
	// because they are what a human reads when asked to confirm the grouping.
	Operation string `json:"operation"`
	Target    string `json:"target"`
	// Scope and Project bound where the capability applies.
	Scope   string `json:"scope"`
	Project string `json:"project,omitempty"`
	// Constraints are the conditions that must hold for the work to be correct
	// ("verify prices against the vendor's page"). Part of identity: the same
	// operation under materially different constraints is different work.
	Constraints []string `json:"constraints,omitempty"`

	Name         string   `json:"name"`
	Reason       string   `json:"reason"`
	Instructions string   `json:"instructions"`
	Verify       []string `json:"verify,omitempty"`

	// SuggestedEvery is a Go duration ("168h") and SuggestedCron a 5-field
	// expression; at most one is set.
	SuggestedEvery string `json:"suggested_every,omitempty"`
	SuggestedCron  string `json:"suggested_cron,omitempty"`

	Effect Effect `json:"code_changes"`
	// Confidence that the work is well-shaped enough to automate at all.
	Confidence float64 `json:"confidence"`
	// Recurrence is the causal claim about whether it will be needed again.
	// Separate from Confidence on purpose: work can be perfectly automatable and
	// still never need doing twice.
	Recurrence Recurrence `json:"recurrence"`
	// ExplicitRequest is the user actually asking for this to be automated,
	// which needs no inference at all.
	ExplicitRequest bool `json:"explicit_request,omitempty"`
	// ClarifyingQuestions are decisions the model could not make for the user —
	// usually scope or side effects. "Should major version bumps be included?"
	// is a different task depending on the answer, and guessing it silently is
	// how an autonomous job does something nobody asked for.
	ClarifyingQuestions []string `json:"clarifying_questions,omitempty"`

	// The four eligibility gates. Repetition alone is NOT enough: a password
	// reset recurs, a vague "make this better" recurs, an emotionally repetitive
	// question recurs, and none of them is a capability worth handing to an
	// unattended machine.
	Bounded        bool `json:"bounded"`
	Reproducible   bool `json:"reproducible"`
	UnattendedSafe bool `json:"unattended_safe"`
	Evaluable      bool `json:"evaluable"`
}

Proposal is a task memcode believes it could take over, with everything needed to create it.

func (Proposal) Eligible

func (p Proposal) Eligible() bool

Eligible reports whether the work qualifies at all, independent of how confident or how often it has been seen.

func (Proposal) FamilyKey

func (p Proposal) FamilyKey() string

FamilyKey is the coarser identity used for SUPPRESSION and for grouping signals: family plus scope plus project, without the constraint detail.

Coarser on purpose. Someone who says "don't suggest this kind" means the kind, not that exact phrasing with that exact constraint list — and an offer that returns next week because one condition differed is the behaviour they were trying to stop.

func (Proposal) Fingerprint

func (p Proposal) Fingerprint() string

Fingerprint is the durable identity signals accumulate against.

Built from MEANING — operation, target, scope, project, constraints — and not from wording. "Update our Anthropic models", "OpenAI added models, update ours" and "is the Fireworks catalog stale again?" share no useful phrasing and are the same capability; a fingerprint over words would file them as three unrelated things and never notice the pattern.

func (Proposal) Gaps

func (p Proposal) Gaps() []Gap

Gaps reports what is missing before this task should be created.

The point is NOT to interrogate the user. Most of a task is inferable and should be inferred; these are the few things where guessing wrong has consequences that only show up later, at 3am, with nobody watching.

func (Proposal) IneligibleReason

func (p Proposal) IneligibleReason() string

IneligibleReason names the first failing gate, for a log line.

func (Proposal) Ready

func (p Proposal) Ready() bool

Ready reports whether the proposal can be created without asking anything.

func (Proposal) SameCapability

func (p Proposal) SameCapability(q Proposal) bool

SameCapability reports whether two proposals describe one capability: the same family key, or near-identical family names within the same scope and project.

func (Proposal) ToTask

func (p Proposal) ToTask(now time.Time) (task.Task, error)

ToTask converts a proposal into a real task definition.

Every field is filled from the proposal or from a documented default, so accepting an offer writes a COMPLETE, valid task and never opens a questionnaire. Anything the proposal did not determine falls to the same defaults a hand-written file would get.

type Recurrence

type Recurrence struct {
	Kind RecurrenceKind `json:"kind"`
	// Cause states WHAT makes it recur, in the model's own words. Required for
	// a prospective offer: an offer that cannot say why is a guess, and it goes
	// in the message so the user can judge the reasoning rather than the verdict.
	Cause string `json:"cause,omitempty"`
	// Confidence in the causal claim, distinct from confidence that the work is
	// well-shaped.
	Confidence float64 `json:"confidence"`
	// Value is what automating it buys — usually "notice X without you having
	// to remember to look".
	Value string `json:"value,omitempty"`
}

Recurrence is the structured reason to expect the work again.

func (Recurrence) Inherent

func (r Recurrence) Inherent() bool

Inherent reports whether the work recurs for a reason that exists in the world, independent of this user's habits. These are the offers that can be made on first contact.

type RecurrenceKind

type RecurrenceKind string

RecurrenceKind is WHY this work would happen again — the causal claim behind an offer, rather than an observation that it happened twice.

This is the difference between a system that notices patterns and one that understands work. "Update our Anthropic models" is obviously recurring the FIRST time it is asked, because upstream vendors change their models whether or not anyone here is watching. Waiting to see it three times before saying so makes memcode look less intelligent than it is.

const (
	// RecurrenceOneOff will not happen again: renaming the product, fixing one
	// specific bug. Bounded, reproducible and evaluable — and still not a
	// standing job, which is exactly why the four gates are not enough on their
	// own.
	RecurrenceOneOff RecurrenceKind = "one_off"
	// RecurrenceExternal recurs because the WORLD changes independently of this
	// repository: vendors ship models, packages release versions, advisories
	// appear, certificates expire. The strongest reason to offer immediately.
	RecurrenceExternal RecurrenceKind = "externally_recurring"
	// RecurrenceInternal recurs because of this project's own rhythm:
	// regenerating a report as data lands, re-running a check as code grows.
	RecurrenceInternal RecurrenceKind = "internally_recurring"
	// RecurrenceUserPattern is not inherent to the work — it recurs because
	// THIS person keeps wanting it. Not knowable on first contact; this is what
	// accumulated history is for.
	RecurrenceUserPattern RecurrenceKind = "user_pattern"
	// RecurrenceUncertain: no confident causal claim either way.
	RecurrenceUncertain RecurrenceKind = "uncertain"
)

type Signal

type Signal struct {
	ID          string
	SessionID   string
	ObservedAt  time.Time
	Project     string
	Family      string
	FamilyKey   string
	Fingerprint string
	Operation   string
	Target      string
	Scope       string
	Confidence  float64
	Evidence    string
	Proposal    Proposal
}

Signal is one observation that a capability might be worth automating.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the durable signal and suppression record.

func Open

func Open(ctx context.Context, path string) (*Store, error)

Open creates or opens the signal store.

func OpenDefault

func OpenDefault(ctx context.Context) (*Store, error)

OpenDefault opens the store at its standard location.

func (*Store) Close

func (s *Store) Close() error

func (*Store) Clusters

func (s *Store) Clusters(ctx context.Context, project string, now time.Time) ([]Cluster, error)

Clusters groups signals into capabilities, newest evidence first.

Grouping is by family key, then MERGED across near-identical family names via SameCapability — so a classifier that named the same thing slightly differently on two occasions still accumulates rather than splitting.

func (*Store) Forget

func (s *Store) Forget(ctx context.Context, p Proposal) error

Forget drops signals for a capability, used when a task is actually created so its own evidence stops re-proposing it.

func (*Store) Prune

func (s *Store) Prune(ctx context.Context, before time.Time) error

Prune drops evidence too old to matter, keeping the store small.

func (*Store) Record

func (s *Store) Record(ctx context.Context, sessionID string, p Proposal, evidence string, now time.Time) (Signal, error)

Record persists one observation.

func (*Store) Suppress

func (s *Store) Suppress(ctx context.Context, p Proposal, now time.Time) error

Suppress records a durable "don't suggest this kind", keyed to the capability rather than the wording.

Declining to automate provider-catalog updates says nothing about whether to automate a weekly dependency audit, so suppression attaches to family, scope and project — never to "automation" in general.

func (*Store) Suppressed

func (s *Store) Suppressed(ctx context.Context, p Proposal) (bool, error)

Suppressed reports whether this capability has been refused durably.

Checks the exact family key and, because a classifier's naming drifts, any suppressed family near enough to be the same capability. Someone who said no once should not be asked again because the wording moved.

func (*Store) Suppressions

func (s *Store) Suppressions(ctx context.Context) ([]string, error)

Suppressions lists what has been refused, for `task suggestions`.

func (*Store) Unsuppress

func (s *Store) Unsuppress(ctx context.Context, family string) (int, error)

Unsuppress lifts a durable refusal.

type Turn

type Turn struct {
	SessionID string
	Project   string
	// Request is what the user asked for, Summary what was done.
	Request string
	Summary string
	// Changed reports whether the turn actually altered the repository — weak
	// but real evidence that the work was substantive.
	Changed bool
}

Turn is what the detector sees after work completes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL