decisionlog

package
v0.1.0-rc.2 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 51 Imported by: 0

README

Local decision capture

The daemon extracts the decisions of the coding-agent sessions running on this machine and keeps them in the shared private <state>/app.sqlite application database (directory 0700, file 0600). Extraction uses the published prompt (decisions.NewPublicExtractor, decisions-public-v1) and the decisions package's renderer, parser and citation validator, so a local extraction follows the same rules as a hosted one.

Gate

A session is captured only when all of these hold, read for every capture:

  • the automatic master switch is on (the desktop tray's pause turns it off);
  • the decisions switch (preferences.decisions.enabled) is not false;
  • the session's repository is picked and its settings.decisions is not false.

Capture is on by default. A session outside a picked repository, or in one turned off, is not read and leaves no row.

When a session is captured

The scheduler reads the live session tracker every 30 seconds; it keeps no per-session timers. A session is queued when:

  • it settled: it ended, or it is not running and its last activity is at least 2 minutes old (its turn ended, it waits on its user, or it went silent); once per last activity;
  • it has been running for 20 minutes since it started running after it last settled, or since it was last queued for running long (a brief wait between prompts does not restart the count);
  • it is about to compact (pre-compact hook event), at once;
  • every 5 minutes, it is settled (the backstop, for a transcript that changed without new activity; an unchanged one is left alone).

One worker captures the queued sessions one at a time; a session already queued is not queued twice. Before a call, the ledger throttles a session: at most one attempt every 5 minutes, none within 10 minutes of a failed one, and after 12 attempts in a day only once it ended. A capture cut short by the daemon stopping settles its attempt as abandoned (outcome abandoned) and is not a failure.

Capture

  1. The transcript is located as the live session view locates it: a regular file inside the agent's own session store, symbolic links resolved. When the hooks named no transcript, the path the last capture stored is tried first, Claude Code and Codex alike; if it no longer holds the session, a Codex session is looked up by id, which scans the store (a Claude Code one cannot be found without a path). The transcript must map to the hook event's session, otherwise the session is skipped (session_mismatch); a path is stored only once it does. A session whose own files exceed 64 MiB is skipped (too_large).
  2. When the stored fingerprint (size and modification time of its files) is unchanged and the session is covered, nothing happens, except that a stored path that failed is replaced by the one found.
  3. baum's own generation runs are skipped (generation_run).
  4. The transcript is rendered and redacted. When it has the head extraction's content hash (an empty head included), the head still covers it and no call is made. When a head exists, the call extends it: the message lists the existing decisions by handle and then the whole transcript, with a marker after the turns the head's prefix covered; existing decisions keep their IDs. Otherwise the call reads the whole transcript.
  5. A prompt estimated over 272,000 tokens is skipped (over_token_cap).
  6. The backend is preferences.decisions.backend, the data extraction backend the app chose, else the one reviews use (config file, app preferences), and auto resolves as for reviews in the desktop app (generate.SelectDesktop): the first of claude and codex that passes its check, never the Anthropic API or cursor-agent; none is backend_unavailable. A requested backend other than auto, claude or codex is refused (backend_unsupported). The model follows the resolved backend: preferences.decisions.model unless it names another provider's family, else claude-haiku-5-5 for claude and gpt-6-luna for codex. A backend that does not pass its check is backend_unavailable. A backend that cannot make a single structured call is backend_unsupported too.
  7. The cost is reserved before the call: the prompt's byte bound and 16,384 output tokens at the model's list price. Claude Code caches the prompt, so claude's call is reserved at the cache-write rate when that is higher. It may make a second call, after the harness rejected its first reply against the schema; that resends the cached prompt, so it is reserved at the cache-read rate plus another reply. The Codex CLI has no option capping the reply, so its 16,384 tokens are an estimate rather than a limit. Prices only report: a model the price list does not price runs, reserved at 0. There is no spending cap: as for reviews, each call keeps its token and time budgets.
  8. The attempt is recorded as pending and the completion made: one request for anthropic; claude -p with no tools besides its structured reply, which normally ends after one call and may retry a rejected reply once; or one codex exec turn with its shell tools off, normally one request (a call to a tool Codex still offers, such as its patch or plan tool, makes another within the turn). The completion, a retry included, has 10 minutes, the default pass_timeout of a review pass: a backend still running then is stopped and the attempt fails (reason timeout), so a hung backend does not hold up the sessions queued behind it. The attempt is settled with its cost from the reported usage, priced as reviews price theirs (pricing.List.OutcomeCost): when any part of it is unknown (a run cut short, a speed or 1-hour cache-write split not reported, or several calls whose prompt sizes may straddle a long-prompt tier) the cost is unknown, not estimated. It is also settled with how many replies the harness rejected against the schema (rejections; the last one's reason goes to the private log with the attempt's stage, item and id). An attempt still pending when the daemon starts is settled as abandoned. A reply stands when the ledger cannot be written after it; the failure is logged.
  9. The reply is masked for secrets before it is parsed, validated against the transcript, given IDs and saved.

A failed call is not cached. After 3 failures in a row the session is marked extraction_failed until its transcript changes. Skips for a missing or unsupported backend are tried again the next day or when the transcript changes; other skips only when it changes.

Stored data

  • capture_session: per agent and session id, the repository and branch, the transcript path, the fingerprint, the head extraction (content and prefix hash, prompt version, backend, model, decisions with IDs, dropped citations, tokens and cost), the coverage (extracted or skipped with a reason) and consecutive failures.
  • decision and the current_decision view, decision_file: the decision log. A decision is upserted by ID; decisions a newer extraction no longer holds get obsolete_at and are never deleted. A decision is dated by its first cited turn's time, else the date its ID had, else the extraction's time.
  • capture_attempt: the ledger of calls (backend, model, full or extend, reservation, cost, tokens, state, failure reason, rejected replies), with the stage that made the call (capture, or a feature log stage) and the item it was about.

Nothing is pruned: the ledger and the decisions, obsolete ones included, are kept for good, as hosted extraction keeps them. The store holds decisions, rationale and evidence quotes derived from redacted transcripts, never the transcripts themselves.

In the app

API serves GET /api/decisions, mounted by internal/uiserver:

  • ?agent=&session=: one session's current decisions, its coverage and skip reason, when its row last changed (updated_at, null when never captured) and capturing, true while one of its calls is pending, and capture_off, why the gate keeps capture from running for its repository now (paused, off, repository_off or not_picked; the repository is the tracker's, where the session works now, else the one it was last captured in). A registry that cannot be read leaves it empty and is logged.
  • ?repository=&pr=: the same for each session of the pull request, resolved when read: the tracked sessions whose pull request it is (the live tracker's prlink link), ended ones it still holds included so their provisional decisions stay, then the sessions its latest saved review links on this machine (locallink, the computation its PR page's sessions list shares; a failure to link is logged and links none, and a review too large to serve links none but still dates the provisional marks). A session in both appears once; live is a tracked session not over. Decisions dated after the review's generated_at are provisional. A decision is dated when it was first raised (its first cited turn, as hosted decisions are), so provisional counts from then, not from when it was last revised; without a saved review none are, and only the tracked sessions are listed.

A decision carries only what the app shows (DecisionView): id, title, summary, category, who, rationale, rejected alternative, superseded, files and date; evidence, turns and branch are not served, nor a session's repository.

Store.Changed is called after every write the app shows (a skip, a cover, a save, an attempt beginning or settling; the beginning is what shows "Capturing…", and the settling ends it when no save or skip follows); the daemon publishes the app's decisions event, which carries no data, with it, and also after the settings or a repository's are saved. The settings are preferences.decisions (enabled, backend and model), served only by GET and PUT /api/settings/decisions (in internal/uiserver), which add EffectiveModel: the backend and model a capture would use, resolved by the same helper a capture uses, auto included, and whether the chosen model is ignored. Its backend checks get 5 seconds; no backend, or none in time (backend_undetermined), is unknown (empty) and ignores nothing. baum internal reviews show reports the chosen backend and model as decisions_chosen_backend and decisions_model, and the effective ones as decisions_backend, decisions_effective_model and decisions_model_ignored; baum internal reviews set --decisions-backend and --decisions-model change them. A repository's own setting is settings.decisions, through the repository settings API. Analytics counts capture's calls in the ledger (Store.Ledger) under baum's work.

Feature log

The feature log groups a repository's pull requests and sessions into features as the hosted feature log does, with the features package's rules and published prompts. Its choices are in featureconfig.go, and it shares the store with capture. FeatureWorker syncs a repository's pull requests, reads their paths and commits from the clone, and links the commits to this machine's sessions, then places them in features, writes the features' text and consolidates them. It runs in the daemon beside the capture scheduler, every minute and when settings change, for each picked repository with an available clone whose decisions are captured (the gate above); a repository turned off is not read and makes no GitHub call. A repository whose listing fails, whose clone cannot be read or whose remote is out of reach is tried again after 15 minutes.

Pull requests
  • The first run for a repository records when the feature log was activated for it (pull_sync.activated_at), for placement to leave earlier work out.
  • The repository's pull requests of every state are listed through the GitHub REST API, most recently updated first, 64 a page (a page too large to read is read again at half the size, down to 8), until one updated before the cursor (the first time, 180 days back); one updated exactly at the cursor is listed again. The first page is read with the stored ETag, so an unchanged repository costs one conditional request. The cursor moves to the latest update listed, or to the start of the window when none was. A listing stops at about 10,000 pull requests; older ones still due are skipped and logged. A first build lists the whole history once besides (see "First build").
  • Only pull requests that can be placed in a feature are read from the clone: open or merged ones not opened by a bot. Each pass fetches what its due pull requests lack, as a review fetches a missing pull request commit: a head from refs/pull/<N>/head into refs/baum/pull/<N>/head and a base by its id into refs/baum/pull/<N>/base, both force-updated, 100 refspecs to a git fetch. So each such pull request of the 180 days before activation (of the whole history after a first build), and every one after, gets refs in the clone; they are kept, not pruned, as they may back saved reviews. Each pull request's commits, from its base to its head, are then read from the clone's own objects, never fetched. Its paths are the files its commits change; its commits replace those recorded. A new base has them read again. GitHub's file list is never read.
  • When a fetch fails, the remote is asked for its HEAD (git ls-remote). When it does not answer, the pass stops, nothing is counted against any pull request and the repository waits; when it answers, the refs of the failed fetch are fetched one at a time, so one the remote lacks keeps out no other. A pull request whose commits then cannot be read (its head or base is missing, or it has too many) is read again after 15 minutes; after 3 failures at its head its paths are unknown at that head. A new head starts the count again, and the daily re-link has every failed pull request read again at once, an unknown one 3 more times. A pull request that fails never stops the others or the linking.
  • The gate is read again before the fetch, before each pull request and before linking; a repository turned off during a pass stops there, and its commits are not linked in that pass.
Sessions linked to pull requests

The commits are linked to the sessions on this machine with the local linkage of the review page (locallink.LinkCommits: checkpoint candidate discovery followed by independent creation or edit evidence; generation runs left out), and their links replace the ones they had:

  • each run, the commits just recorded, made in the last 7 days (hosted looks back a day; a laptop may be off for days);
  • every 15 minutes, the commits within the session window of now, which a session still running may yet link to, read from the clone without fetching, at the head their commits were recorded at;
  • once a day, every commit of the last 7 days.

A commit linked to a session more than one way keeps its strongest link.

Store.SessionLinks reads what attributes a captured session's decisions (features.SessionLinks): the feature pull requests its linked commits are in, each with the paths it touches and those it was assigned with; a session with no capture row has none.

Model calls

Placement, feature text and consolidation call as capture does (steps 6 to 8 above): the same backend, model, reservation (the request's byte bound and the call's reply bound: 4,096 tokens for a placement, 32,768 for a batch, merge or split, 8,192 for a text or pull request summary), 10-minute bound and ledger, each attempt recorded with its stage (feature_assign, feature_summary, pr_summary or feature_consolidate) and item. Rejected replies and a ledger that cannot be written are handled as capture's. The preferences and the repository's settings are read again before every call, so a repository turned off makes no further call. A repository turned off or a missing backend stops the stage until its next run, with no failure counted.

Placement

After the sync and the linking, each run places at most 100 pending items, oldest trigger first, one call each with the published assigner prompt. An item is pending when:

  • a pull request is in no feature, was opened at or after the activation and within the last 7 days (whatever its state), was not opened by a bot, and its paths are known or it opened 15 minutes ago;
  • a pull request the assigner placed has merged since, once;
  • an open pull request's head is not the one it was placed at and its paths changed enough (features.LargeUpdate with the paths it was placed with, 0.5 and 3); a head that changed too little is recorded as checked (checked_head_sha) and left until the next push;
  • a captured session with current decisions, extracted in the last 7 days and not since 15 minutes ago, is in no feature and linked to no pull request's commit. A session linked to one is never placed on its own: it follows that pull request's feature.

Its candidates are at most 16 features of the live version not merged into another, changed in the last 90 days (a change to the feature, or one of its pull requests or their placements), or the item's current feature. A feature's paths are those its pull requests were placed with and those cited by the decisions of its sessions placed on their own that follow no feature pull request. Each of the item's paths a feature holds scores one over the number of these features holding it. The current feature comes first, then the features holding a pull request linked to the item (stacked on it or it on them: one's base branch is the other's head branch, other than the default branch (none while the default branch is not known), both in the repository rather than a fork, and opened from by one pull request only; sharing a linked session; or mentioned as #N), best path score first, then the best path scores of the rest (10), then the most recently changed. Each candidate shows its digest and its pull requests' titles with their saved review summaries, else generated ones.

The reply joins a candidate, starts a feature with its provisional text, or skips a small change. A session only joins: with no candidate it is settled without a call, and a reply starting a feature or skipping settles it. A skipped pull request in no feature is settled for its head; one already in a feature stays in it, as if the reply chose it. Settled items wait for their next trigger (feature_settle). A failed call, an invalid reply or a failed write after a call backs the item off for 10 minutes; after 3 counted failures for one trigger it waits for the next.

The preferences and the repository's settings are read again before each item, so a repository turned off stops the run. A call cut short by the daemon stopping counts no failure. A pull request placed at its head before its paths were known gets them as the paths it was placed with once they are recorded, with no call.

A placement marks every feature it changes: the one joined, the one left and the features of the pull request's linked sessions; an identical one marks none. Also marked: a session's features when its decisions are saved; a feature pull request's feature when its title, description or state changes (in the current version and in a building one), and, with the features of the sessions linked to its old and new commits, when its commits or paths change; and when the sessions linked to a commit change, the features holding a pull request with it and the features of those sessions (an unchanged re-link marks none). Marking raises membership_version and sets dirty_since and changed_at.

Feature text

Each run writes the text of at most 50 features behind their members, not changed for 15 minutes, longest behind first, 4 at once, with the published summary prompt. A stop or an error starts no further feature; those being written finish. A feature's input is its pull requests, earliest opened first (title, description, state, and the summary of the latest review saved in the clone, else a generated one), the current decisions of its sessions that attribution gives it (what was decided and why and the cited files, never a session's prompts or quoted evidence) and its paths. The call also reads the repository's name and the feature's current title and description.

A pull request with no saved review summary gets one from its own call, from its title, description and the feature's decisions of the sessions linked to it, kept in pr_summary with its input's hash and reused while that is unchanged; one with neither a description nor such a decision gets none.

The text is kept with the hash of its input and its own text, decisions named by session and first cited turn, so the same decisions extracted again under new IDs keep it. Key decisions are kept by ID and address; reused text takes the IDs of the current decisions at their addresses. When a feature's input hash is unchanged, its text is kept and it is marked clean without a call. The text is stored only when the feature's members did not change during the call; otherwise it stays behind for the next run. Up to three key decisions are kept by ID. A feature with neither pull requests nor decisions keeps its text, loses its key decisions and is marked clean. A failed pull request summary still writes the text, leaving the feature behind; failures back off and count as placement's do, per membership version.

Consolidation

After the text, a repository's features are consolidated as hosted ones are, with the published consolidation prompt, at most once every 7 days (from the start of the last pass) and only when their fingerprint (every feature's id, membership and text versions and merged_into, merged ones included) changed since the last pass (feature_consolidation). A pass's own merges and splits are changes, so one that changed features is followed by another a week later. A pass reads the features not merged into another and active in the last 90 days (as placement's candidates), most recently active first. Its calls' replies may run to 32,768 tokens rather than hosted's 8,192: Claude Code's models think within the reply's bound, and a merge over a first build's features needs the room.

  • Merge. With two features or more, one call sees each feature's text, digest, paths, pull requests with their summaries (the saved review's, else the generated one) and how many sessions are in it on their own. Each group's duplicates are merged into its survivor: their pull requests and sessions move to it as consolidation's, the duplicate gets merged_into (and so do the features merged into it before), both are marked, and a link to the duplicate resolves to the survivor (ResolveFeature).
  • Split hold. For four weeks after a split, a merge of one of its parts with the feature it was split from, or of two of its parts, is dropped from the reply; the merge call marks such a part with its origin. A part merged into another feature passes its origin and split time on.
  • Split. At most 3 features, as they will be after the merges, with more than 30 pull requests or pull requests opened more than 8 weeks apart, largest first, get one split call each (a call takes its slot whatever its outcome) with their pull requests' text, state, opening date, placed paths and summaries. A feature kept whole, or too large to offer (over 200 pull requests or its input over its bound, which takes no slot), is recorded as checked (split_checked_version) and not offered again until its members change; a survivor of the pass's merges always is. Otherwise the largest part keeps the feature's id (ties to the part with the earliest opened pull request), and each other part becomes a new feature with the reply's text, recording split_from and split_at, its pull requests moved as consolidation's at the head and paths they were placed with; sessions in it on their own stay. The parts are recorded as checked.
  • Writing. Every call of a pass is made before anything is written. Each merge and split is written in its own transaction, only while its features are at the membership versions the pass read (as its own earlier writes left them) and a split's feature holds exactly the pull requests it partitioned; otherwise that one is skipped and the pass goes on. Writes mark every feature they change and the features of the sessions linked to the pull requests they move, for the text stage.
  • Failures. A call that fails, or a merge reply that does not decode, defers the whole pass: nothing is written (a split call's failure or refusal discards the merges paid for, which are logged and counted), and the pass backs off as placement's failures do, counted against the fingerprint. A split reply that is invalid leaves only that feature whole and unchecked. A call that cannot be made (a backend reason) also writes nothing; the pass is made again on the next run.
First build

The live placement places only work opened since the feature log was activated. A repository's history is placed when the user asks for it, as a hosted rebuild does, by a first build: a job of the job queue (jobs.KindFeatureBuild) that starts at once when the user asks for it, with no estimate, as every requested run does, or baum internal features build.

  • History. The build first lists the repository's whole pull request history, once per repository (pull_sync.history_listed_at), with no ETag and without moving the sync's ETag or cursor, never replacing a pull request a sync saved with a later update; a listing that stops at its bound, leaving out the least recently updated pull requests, is reported (history_truncated) and not retried. The build then reads the paths of every pull request due, as a sync does, and, before it places any, links to this machine's sessions those of the commits it read that were made in the last 7 days (FeatureLookback), as a sync links its fresh commits; older commits are not linked.
  • No cap. As for a requested review, there is no spending cap and nothing about the price stops a build: a model with no price runs as any other, and the build runs to the end. Costs are recorded at API prices where they are known. A repository whose decisions are not captured, or whose backend cannot call, does not start (the job is refused with the reason).
  • Dry run. --dry-run counts what a build would do, with no cost: the pull requests left to place and its calls, a batch call per 25, the new features' text twice (a feature per 3 pull requests), a resumed version's features whose text is behind, and a summary of each pull request with a description; and whether it runs discovery first. It lists the history, saving the pull requests it lists and activating the repository's sync state; it writes no feature.
  • Seeding. A new version, with no features and no seeds, is seeded with a feature taxonomy (--taxonomy, default auto): the newest made after the version, else a new discovery's (see "Discovery"); latest or a taxonomy's id a stored one; none nothing. Each of its features becomes a provisional feature with no members, whose text is the taxonomy's, and a seed (feature_seed, with its paths); the version records the taxonomy. A resumed version is never seeded again. A discovery that cannot run or fails leaves the build unseeded, with the reason in its report (unseeded), recorded on the version so a resume neither runs nor pays for it again. The gate is read right before discovery, as before any call.
  • Building. The build makes the repository's building version, or resumes the one there is, and places in it the merged and open pull requests of the whole history not opened by a bot, in no feature of the version and not skipped or left out of it at their head, listed once when it starts, oldest first, in batches of 25 with the batch prompt (its seeded form when a candidate has no pull requests). Each item's candidates are ranked against the version as placement ranks them, read again for every call, and the call offers their union, each item's best first, at most 40. Seeds no pull request joined are kept out of the ranking: each is offered, with its text and paths and no pull requests, to an item by its path score (discover.PathScores), before the first ranked candidate not linked to the item with a lower score, as the hosted rebuild does. A seed a pull request joined is an ordinary feature from then on. A reply's new feature is created for the first item that starts it; a skipped small change is settled in the version at its head. The live stages keep using the current version meanwhile.
  • Failures. A batch call that fails, for whatever reason, is made once more, then the batch is split in halves down to one pull request; one that still fails is tried once more with the others after the last batch, in batches of 25, and then left out (settled in the version as left_out). A failed store write stops the build.
  • Text. Every 3 batches and after the last, the text of every feature of the version behind its members is written, without the debounce; a feature whose text fails backs off and is left for the text stage. A build makes no consolidation pass.
  • Switching. The seeds no pull request joined are deleted, then the building version becomes current in one transaction: the current one is retired, the skips become placement's settlements, the pull requests left out lose theirs so placement takes those within its lookback, and the repository's last consolidation pass is forgotten, so the weekly pass consolidates the new version on its next run, a minute or so after the switch. A live stage that raced the switch never writes the retired version: its run stops at its next item or write (version_retired), and a consolidation pass writes, records and settles nothing, each write checking the version in its own transaction. Work the live stages placed in the old version is in no feature of the new one; placement takes what is within its lookback. A retired version is kept, and a link to one of its features resolves as archived.
  • Stopping. A build stops before a call that cannot be made (disabled or a backend reason; failed for a store error), when a fetch cannot sign in to the remote because the credential needs a key passphrase or password nobody can type in the background (git_auth_ssh, git_auth_https; the Timeline says how to make it available), or when it is cancelled or the daemon stops, leaving the version building with stopped set to why; starting it again resumes it. Its job then fails with that reason. Progress (phase, calls) is reported to the job, and the version records its pull requests placed of the total and its spend over all its runs (spent_usd, each call's known cost, and unpriced_calls, those whose cost is unknown: the spend is then a lower bound, reported as such to the job). A spend the version cannot record is logged; the call and its ledger row stand.
Discovery

Discovery maps a repository into its features, as hosted discovery does (features/discover), and stores the reply as a taxonomy (feature_taxonomy). It runs as a first build's first step or with baum internal features discover, which first lists the history and reads its paths as a build does.

  • Its backend and model are the other feature calls' (see "Model calls"): the data extraction backend and model, whatever a build's own --backend and --model. cursor-agent is not supported; a model with no price runs as any other.
  • The default branch (the latest one GitHub reported for a pull request) is fetched into refs/baum/default. The run directory holds its head's checkout in head/, bounded as a review's, and history.jsonl (read-only), the history digest of the pull requests a build places: titles, branches and paths, no descriptions.
  • It runs as a review's agent task (generate.RunTask): the published prompt feature-discover-public-v1, the discovery schema and the taxonomy's bounds as its check, and a review run's budgets: the configuration's run timeout, calls and tokens (by default 45 minutes, 40 calls and 2,000,000 tokens), 3 provider attempts and 2 repairs, whatever the model's price. Hosted discovery keeps its own budgets. Its output is masked before it is read.
  • It is one ledger row (stage feature_discovery); in a build its cost is added to the version's spend.
Stored data
  • pull_request, pull_commit, pull_sync: the listed pull requests with their paths at a head and failed reads, their commits, and the listing's ETag, cursor, activation, last full re-link and history listing.
  • session_commit: each session linked to a commit, with the method and confidence.
  • feature_version, feature, feature_pull, feature_session, feature_settle, pr_summary, feature_consolidation: the tables placement, text, consolidation and a build write.
  • feature_taxonomy, feature_seed: discovery's taxonomies and the seeds of the versions built from them (feature_version.taxonomy_id). A repository has at most one current and one building version; featureVersion is the one place that chooses the version a stage uses (the live stages' current one, made when first needed, and a build's building one).

These rows are not pruned. Only placement, text, consolidation and a build write features, except SeedSample, which fills a store of the tour's sample project (internal/sampleapp) as a build would have left it.

Timeline API

Timeline serves the app's Timeline page under /api/timeline/, mounted in the local API behind its authentication, in the view models of the review viewer's decision log (oss/viewer/src/decision-log.ts). It reads the store and never writes it.

  • GET /repositories: decisions_enabled (the decisions switch), repositories, the picked repositories whose decisions are not turned off (the switch and the repository's setting; the automatic master switch is not read, so pausing hides nothing), by name, each with has_features (its current version has a feature not merged into another), and turned_off, those whose own setting is off. A list reads the repositories its repository parameters name (repeated, any case; none is every one of them), and one it does not serve is a 400; a detail is found in any of them, and is a 404 in another. Every row and detail names its repository, {"id","full_name"}, both the registry's name.
  • GET /features?repository=&q=&person=&file=&from=&to=&status=&cursor=&limit=: the repositories' features of their current versions not merged into another, merged in one order: by last activity (else creation) newest first, then id, 50 a page (limit, at most 500). cursor is the last row's time and id, so a page follows on from it whatever arrived meanwhile. status (in_progress, shipped, abandoned; repeated or comma separated), q, person (below), file (a pull request touching the path or anything under it, or a decision of the feature citing one) and from/to (the features whose activity, else creation, overlaps them, as hosted) filter before paging. A row has its title and description (null while none is written; summary_pending while it is behind its members), the status (features.DeriveStatus over its pull requests), the first and last activity (its pull requests' opening and last update and its decisions' times), people (the user, when it has a session or a pull request they opened, then its other pull requests' authors not opened by a bot, by login), the counts of pull requests, sessions (the repository's captured sessions linked to its pull requests' commits, or in it on their own) and decisions, and its key decisions, read as the text names them (currentKeys). A session's decisions are attributed as the feature's text attributes them (features.Attributor), each to one feature and, within it, to the pull request Attributor.PullRequest chooses.
  • GET /features/{id}: the row with digest, its pull requests newest first (state, draft, merged, the latest saved review's state against the head and its summary, else the generated one, and the decisions shown under each) and its sessions: those in it on their own, and any whose attributed decisions fall under none of its pull requests. The id is resolved by Store.ResolveFeature: a feature merged into another answers with the survivor's detail, one of a retired version {"archived": true}, and one of a version being built, or a chain of merges too long to follow (ErrMergeChain), is not found.
  • GET /build?repository= (exactly one): building, the repository's first build in progress (its building version: pull requests placed of total, why it stopped, "" while it may go on, and spent_usd over all its runs), null without one; has_features, whether the current version has a feature not merged into another; and current_since, when it became current (null without one). A building version's features are not listed until it is current.
  • GET /decisions?repository=&q=&person=&file=&who=&category=&superseded=&from=&to=&cursor=&limit=: the current decisions of the repositories' captured sessions, newest first and then by id, paged as features are, with coverage, summed over the repositories: their sessions extracted, skipped by reason, and being read (a capture attempt pending). file is the path or anything under it; who and category repeat or are comma separated; superseded decisions show only with superseded=1 (or true). On both lists from (inclusive) and to (exclusive) are RFC 3339 times or dates, a date being the local day's start; another value is a 400.
  • GET /files?repository=&prefix=&limit=: files, the paths the repositories' current decisions cite that start with prefix, and their folders (ending in /) that do, each with its kind (file or folder) and decision_count, the decisions citing it or anything under it, each counted once (superseded ones included, as hosted counts them): most first, folders before files, then by path; 20 (limit, at most 50).
  • GET /decisions/{id}: its session (<agent>:<id>, as the app's session page names it, branch and person), the session's current decisions (summary, category, who, rejected alternative, files, turns) and the pull requests its linked commits are in. A decision a later capture replaced (obsolete, kept for good) still opens, as on the hosted Timeline: with the decisions replaced with it and replaced: true. turns are the human prompts the decisions cite (ordinal, triggered_at, text), read from the session's transcript on this computer (its stored transcript_path, found as capture finds it, livesessions.Locate) and rendered as capture renders them (decisions.Render with a bundle.Redactor): the harness's wrappers stripped, secrets and identities masked, and a turn that was only wrappers left out. A session's prompts are kept by its transcript's version (Location.Version), the 16 sessions last read. When the transcript is no longer on this computer, cannot be read, or its files are over capture's MaxTranscriptBytes, prompts_withheld is true and turns empty. No rationale or evidence is served.
  • GET /sessions/{agent}:{id}/transcript: a captured session's transcript, as GET /api/live-sessions/{agent}/{id}/transcript answers it, whether the tracker still lists the session or it ended long ago (livesessions.Transcripts.Read, from its stored transcript_path; live from the tracker). A session of a repository the Timeline does not serve, or one never captured, is a 404.

q matches keywords, each word a prefix and all of them required, with SQLite FTS5 (the unicode61 tokenizer, diacritics folded, no stemming, so a prefix matches as typed): a decision's title and summary; a feature's title, description and digest or a pull request's title, or a pull request's number. The index (decision_search, feature_search, pull_search) is kept by triggers on those tables and built from the rows already stored when a store without it is opened.

Every captured session is this computer's user's, so the Timeline knows one person besides the pull requests' authors: the user, named by the first served repository's clone (git user.email and user.name; avatars are never read, the app draws initials). Every served clone's address names them too, and so does their GitHub login's address (<login>@users.noreply.github.com) once Login knows the login (the daemon's is the viewer of a picked repository's cached discovery snapshot, else the GitHub connection's): the pull requests they opened are then theirs, and they are the login's person. It is read at most every 30 seconds. A pull request's author is named by their login's address.

  • GET /people?repository=: people, the user with decision_count, the repositories' current decisions, superseded ones included as hosted counts them (the list hides those unless asked); nobody without a decision or a known user.
  • GET /features/people?repository=: people, everyone among the repositories' features' people with feature_count, most first, then by address.
  • person= (an address, any case) on /features keeps the features that person is among the people of: the user (by any of their addresses) those with their sessions or pull requests, anyone else those with a pull request they opened. On /decisions the user keeps every decision, anyone else none.

A request reads its features' sessions' current decisions and links in a few set queries (Store.Current and SessionLinks read the same way), a feature's detail only that feature's, and opens each repository's clone at most once (LocalClones): its git identity and its review store are each read at most once.

Changed is called by the capturer once a capture's call is recorded as pending, by the capture scheduler after a capture that saved decisions, recorded a skip or settled an attempt, and by the feature worker once a repository's pass, consolidation included, listed pull requests, linked sessions, placed an item, wrote a feature's text, or merged or split a feature. The daemon publishes a timeline event on the app's event stream then, at most once a second, and after a settings change.

Testing by hand

baum internal decisions capture --agent <agent> --session <id> [--path <transcript>] [--dry-run] [--backend <b>] [--model <m>] runs one capture without the gate, throttle or unchanged check and prints its report as JSON. --dry-run stops before the call and writes nothing but the --debug-dir files: it reads an existing store read-only, and uses an empty one in memory when there is none. The hidden --debug-dir <dir> creates the private directory (0700 on Unix, a current-user-only DACL on Windows) and writes the rendered transcript, and after a call the message sent and the raw reply, there, replacing a previous capture's files; a capture that stops before rendering leaves it empty.

baum internal features sync --repo <owner/name> [--clone <path>] [--days N] runs one pull request sync without the gate (the first lists the last N days, default 180) and baum internal features link --repo <owner/name> [--clone <path>] links every commit of the last 7 days; each prints its counts as JSON. baum internal features assign --repo <owner/name> [--clone <path>] [--dry-run] [--limit N] [--backend <b>] [--model <m>] runs placement once, ignoring the activation, and prints each item; --dry-run prints each item's candidates and input size, makes no call and writes nothing. baum internal features summarize takes the same flags but --dry-run and writes the text of the features behind, without the debounce. baum internal features consolidate takes --repo, --clone, --backend and --model and makes one pass over the current features without the interval or fingerprint check. baum internal features build --repo <owner/name> [--clone <path>] [--dry-run] [--backend <b>] [--model <m>] [--taxonomy auto|none|latest|<id>] runs a first build without the gate, a line a batch on standard error and its report as JSON; --dry-run lists the history, saving the pull requests it lists, and prints the pull requests and calls a build would make, with no cost, writing no feature. baum internal features discover --repo <owner/name> [--clone <path>] [--show latest|<id>] lists the history and reads its paths as a build does, then runs discovery once and prints its report, or prints a stored taxonomy. These read the preferences and the repository's settings and fail when they cannot; sync and link read neither. The clone defaults to the repository's registered one. They are for testing, with the daemon stopped: they write the daemon's store.

Documentation

Overview

Package decisionlog captures the decisions of the coding-agent sessions on this machine and keeps them in a private local store. See README.md.

Index

Constants

View Source
const (
	// CaptureOffPaused is the automatic master switch off (the tray's pause).
	CaptureOffPaused = "paused"
	// CaptureOffSwitch is the decisions switch off.
	CaptureOffSwitch = "off"
	// CaptureOffRepository is the session's repository turning it off.
	CaptureOffRepository = "repository_off"
	// CaptureOffNotPicked is a repository that is not picked, or none.
	CaptureOffNotPicked = "not_picked"
)

Why capture does not run for a session (SessionDecisions.CaptureOff).

View Source
const (
	ReasonTooLarge      = "too_large"
	ReasonUnreadable    = "unreadable"
	ReasonMismatch      = "session_mismatch"
	ReasonGenerationRun = "generation_run"
	ReasonOverTokenCap  = "over_token_cap"
	ReasonFailed        = "extraction_failed"
	// Transient.
	ReasonUnsupported = "backend_unsupported"
	ReasonUnavailable = "backend_unavailable"
)

Skip reasons. The transient ones are tried again the next day, or when the transcript changes; the others only when it changes.

View Source
const (
	OutcomeDisabled    = "disabled"
	OutcomeThrottled   = "throttled"
	OutcomeUnavailable = "unavailable"
	OutcomeUnchanged   = "unchanged"
	OutcomeSkipped     = "skipped"
	OutcomeHit         = "hit"
	OutcomeDryRun      = "dry_run"
	OutcomeExtracted   = "extracted"
	OutcomeFailed      = "failed"
	// OutcomeAbandoned is a capture the daemon stopping cut short; its
	// attempt is abandoned and nothing else is recorded.
	OutcomeAbandoned = "abandoned"
)

Outcomes of a capture.

View Source
const (
	MinInterval  = 5 * time.Minute
	FailureDelay = 10 * time.Minute
	MaxPerDay    = 12
	MaxFailures  = 3
)

Throttle bounds.

View Source
const (
	TriggerOpened  = "opened"
	TriggerMerged  = "merged"
	TriggerUpdated = "updated"
	TriggerQuiet   = "quiet"
)

Triggers of a placement.

View Source
const (
	FeatureAssigned = "assigned"
	FeatureCreated  = "created"
	FeatureSkipped  = "skipped"
	FeatureDeferred = "deferred"
	FeatureGivenUp  = "given_up"
	FeatureDryRun   = "dry_run"
)

Outcomes of an item placed or a feature's text.

View Source
const (
	StageAssign      = "feature_assign"
	StageSummary     = "feature_summary"
	StagePullSummary = "pr_summary"
)

Ledger stages of the feature log's calls.

View Source
const (
	BuildReading     = "reading"
	BuildDiscovering = "discovering"
	BuildPlacing     = "placing"
	BuildSummarizing = "summarizing"
	BuildSwitching   = "switching"
)

Phases of a build.

View Source
const (
	TaxonomyAuto = "auto"
	TaxonomyNone = "none"
)

Taxonomy choices of a build besides LatestTaxonomy and an id.

View Source
const (
	// AssignVersion, SummaryVersion and ConsolidateVersion are the published
	// prompts the feature log calls.
	AssignVersion      = assign.PublicVersion
	SummaryVersion     = summary.PublicVersion
	ConsolidateVersion = consolidate.PublicVersion

	// FeatureTick is how often the feature worker runs its stages.
	FeatureTick = time.Minute
	// FeatureCandidates is how many candidate features an assignment offers
	// (K); FeaturePathSlots and FeatureRecentSlots split them between the
	// candidates ranked by shared paths and the most recent.
	FeatureCandidates  = 16
	FeaturePathSlots   = FeatureCandidates * 5 / 8
	FeatureRecentSlots = FeatureCandidates - FeaturePathSlots
	// FeatureCandidateWindow is how recently a candidate feature must have
	// changed.
	FeatureCandidateWindow = 90 * 24 * time.Hour
	// FeatureLookback is how far back commits are linked to sessions, and
	// pull requests and sessions are picked up for assignment. Hosted looks
	// back a day; a laptop may be off for days, so locally it is a week.
	FeatureLookback = 7 * 24 * time.Hour
	// FeatureQuiet is how long a session must have been unchanged before it
	// is placed, how long a pull request without known paths waits for them
	// and how long a feature's members must have been unchanged before its
	// text is written; it is also the debounce of the session window's
	// re-link.
	FeatureQuiet = 15 * time.Minute
	// FeaturePathTries is how many failed reads of a pull request's commits
	// at one head leave its paths unknown until the daily re-link.
	FeaturePathTries = 3
	// FeatureRelinkEvery is how often every commit of the lookback is linked
	// again.
	FeatureRelinkEvery = 24 * time.Hour
	// FeatureReassignShare and FeatureReassignPaths are the large-update rule
	// (features.LargeUpdate) for assigning a pull request again.
	FeatureReassignShare = 0.5
	FeatureReassignPaths = 3
	// FeatureAssignRunLimit and FeatureSummaryRunLimit bound the items one
	// run of placement and the features one run of the text stage take.
	FeatureAssignRunLimit  = 100
	FeatureSummaryRunLimit = 50
	// FeatureSummaryConcurrency is how many features the text stage writes
	// at once: each feature's text and pull request summaries are calls of
	// their own. Fewer than a hosted rebuild's, as each local call may be a
	// CLI process on the user's machine and plan.
	FeatureSummaryConcurrency = 4
	// FeatureBackoff is how long an item or feature whose call failed
	// waits; after FeatureMaxFailures counted failures for one trigger it
	// waits for the next.
	FeatureBackoff     = 10 * time.Minute
	FeatureMaxFailures = 3
	// FeatureBatch is how many items one batch assignment holds.
	FeatureBatch = 25
	// FeatureConsolidateEvery is how often a repository's features are
	// consolidated, when they changed.
	FeatureConsolidateEvery = 7 * 24 * time.Hour
	// FeatureSplitPulls and FeatureSplitAge make a feature a split
	// candidate: more pull requests than that, or pull requests opened
	// further apart. A pass offers at most FeatureMaxSplits features.
	FeatureSplitPulls = 30
	FeatureSplitAge   = 8 * 7 * 24 * time.Hour
	FeatureMaxSplits  = 3
	// FeatureConsolidateOutputTokens bounds a merge or split reply. Hosted
	// uses consolidate.MaxOutputTokens; locally Claude Code's models think
	// within the reply's bound whatever thinking is set to, and a merge over a
	// first build's features ran out of it, so local calls get more room.
	FeatureConsolidateOutputTokens = 4 * consolidate.MaxOutputTokens
	// FeatureSplitHold is how long after a split a merge undoing it is not
	// applied.
	FeatureSplitHold = 4 * 7 * 24 * time.Hour
	// FeatureFirstSyncWindow is how far back the first sync lists a
	// repository's pull requests; a first build lists its whole history
	// (FeatureWorker.ListHistory).
	FeatureFirstSyncWindow = 180 * 24 * time.Hour
	// FeatureBuildTries is how many times a first build makes a failed batch
	// call before splitting it; FeatureBuildCandidates is the most
	// candidates a batch call offers; FeatureBuildSummarizeEvery is how many
	// batches go between its text passes.
	FeatureBuildTries          = 2
	FeatureBuildCandidates     = 40
	FeatureBuildSummarizeEvery = 3
)

The feature log's choices, all in one place (README.md, "Feature log"). They follow the hosted feature log's unless a comment says otherwise.

View Source
const (
	FeatureEstimatePullsPerFeature = 3
	FeatureEstimateTextPasses      = 2
)

What a build's estimate assumes of what it cannot know beforehand (local only; hosted builds have no estimate): the pull requests a new feature gathers, and how many times a new feature's text is written.

View Source
const (
	ConsolidationDone      = "consolidated"
	ConsolidationNotDue    = "not_due"
	ConsolidationUnchanged = "unchanged"
	ConsolidationWaiting   = "waiting"
)

Outcomes of a consolidation pass besides those of placement.

View Source
const (
	ReasonNoDefaultBranch = "no_default_branch"
	// ReasonDiscoveryFailed is followed by the failure's category.
	ReasonDiscoveryFailed = "discovery_failed"
)

Reasons discovery was not run or gave no taxonomy, besides the backend reasons.

View Source
const (
	VersionBuilding = "building"
	VersionCurrent  = "current"
	VersionRetired  = "retired"
)

Feature version states.

View Source
const (
	FeatureSummarized = "summarized"
	FeatureReused     = "reused"
	FeatureStale      = "stale"
	FeatureEmpty      = "empty"
)

Outcomes of a feature's text besides those of placement.

View Source
const (
	// MaxInputTokens caps a transcript's estimated prompt.
	MaxInputTokens = 272_000
	// MaxOutputTokens caps the reply, and is reserved in full.
	MaxOutputTokens = 16_384
	// MaxTranscriptBytes caps the session's own files.
	MaxTranscriptBytes = 64 << 20
)

Limits of one capture.

View Source
const (
	ReasonDisabled    = "disabled"
	ReasonPreferences = "preferences_unreadable"
)

Reasons a feature stage call was not made, besides the skip reasons.

View Source
const (
	ReasonGitAuthSSH   = "git_auth_ssh"
	ReasonGitAuthHTTPS = "git_auth_https"
)

Reasons a build stopped because a fetch could not sign in to the remote (see gitlocal.AuthError).

View Source
const (
	// SettleAfter is how long a session that is not running must have been
	// quiet: its turn ended, it waits on its user, or it went silent.
	SettleAfter = 2 * time.Minute
	// RunningEvery captures a session that keeps running.
	RunningEvery = 20 * time.Minute
	// BackstopEvery looks again at every settled session.
	BackstopEvery = 5 * time.Minute
	// TickEvery is how often the triggers are evaluated.
	TickEvery = 30 * time.Second
)

Trigger timings (README.md, "When a session is captured").

View Source
const (
	CoverageExtracted = "extracted"
	CoverageSkipped   = "skipped"
)

Coverage of a session.

View Source
const (
	AttemptPending   = "pending"
	AttemptSucceeded = "succeeded"
	AttemptFailed    = "failed"
	AttemptAbandoned = "abandoned"
)

Attempt states.

View Source
const (
	TimelinePageSize = 50
	TimelineMaxPage  = 500
	// TimelineFilePage and TimelineMaxFilePage bound /files, as hosted.
	TimelineFilePage    = 20
	TimelineMaxFilePage = 50
)

TimelinePageSize is how many rows a list answers with unless asked for more; TimelineMaxPage is the most it answers with.

View Source
const CallTimeout = 10 * time.Minute

CallTimeout bounds one model call, as review generation's default pass_timeout bounds one pass: a backend that hangs releases the worker and the attempt fails.

View Source
const LatestTaxonomy = "latest"

LatestTaxonomy names a repository's newest taxonomy.

View Source
const MaxCachedPrompts = 16

MaxCachedPrompts is how many sessions' prompts the Timeline keeps, the least recently read making room.

View Source
const ReasonVersionRetired = "version_retired"

ReasonVersionRetired stops a live run whose version a build's switch retired; the next run uses the new current version.

View Source
const StageCapture = "capture"

StageCapture is the ledger stage of a decision capture's call.

View Source
const StageConsolidate = "feature_consolidate"

StageConsolidate is the ledger stage of consolidation's calls.

View Source
const StageDiscovery = "feature_discovery"

StageDiscovery is the ledger stage of a discovery task.

Variables

View Source
var ErrMergeChain = errors.New("merged too many times over")

ErrMergeChain is a feature merged into others too many times over to follow: a broken chain.

Functions

func DefaultDecisionModel

func DefaultDecisionModel(backend string) string

DefaultDecisionModel is the model decision capture uses with backend when the preferences name none: a small, cheap one of the backend's provider.

func LocalClones

func LocalClones(registry TimelineRegistry) func(context.Context, string) TimelineClone

LocalClones is a Timeline's Clone: the first available clone of the picked repository, whose git user.email names this machine's sessions and whose review store holds its saved reviews.

Types

type API

type API struct {
	Store *Store
	// Registry, when set, says whether capture runs for a session's
	// repository, as the capture gate reads it.
	Registry Registry
	// Live lists the tracked sessions; nil is none.
	Live func() []livesessions.Session
	// Linked is nil to link none.
	Linked LinkedSessions
	// Log is nil for slog.Default.
	Log *slog.Logger
}

API serves GET /api/decisions: what the app shows of the captured decisions. Like the other local APIs it does not authenticate; mount it only behind the daemon's authenticated, Host/Origin-checked middleware.

func (*API) Handler

func (a *API) Handler() http.Handler

Handler serves GET /api/decisions?agent=&session= (one session) and GET /api/decisions?repository=&pr= (the sessions of a pull request: those the tracker links to it, ended ones included, and those its saved review links). Decisions dated after that review was generated are provisional.

type AssignReport

type AssignReport struct {
	Repository string `json:"repository"`
	// SmallUpdates are the heads judged too small a change to place again.
	SmallUpdates int          `json:"small_updates"`
	Items        []PlacedItem `json:"items"`
	// Stopped is the reason no further call was made (a backend or price
	// reason).
	Stopped string  `json:"stopped,omitempty"`
	Spent   float64 `json:"spent_usd"`
}

AssignReport says what Assign did.

type Attempt

type Attempt struct {
	Agent, ID, Repository string
	Backend, Model        string
	// Mode is "full" or "extend".
	Mode     string
	Reserved float64
	// Stage is the call's stage, "" for capture; Item names what a feature
	// stage's call was about.
	Stage, Item string
}

Attempt is one model call in the ledger.

type BuildEstimate

type BuildEstimate struct {
	Repository string `json:"repository"`
	// Version is the building version a build resumes, 0 for a new one.
	Version int64 `json:"version,omitempty"`
	// PullRequests are those left to place, in Batches calls.
	PullRequests int `json:"pull_requests"`
	Batches      int `json:"batches"`
	// Calls are the model calls left, besides discovery's.
	Calls   int    `json:"calls"`
	Backend string `json:"backend,omitempty"`
	Model   string `json:"model,omitempty"`
	// Reason is why no call could be made (a backend reason).
	Reason string `json:"reason,omitempty"`
	// HistoryTruncated says the history listing stopped at its bound: the
	// least recently updated pull requests are missing, and the listing is
	// not retried.
	HistoryTruncated bool `json:"history_truncated,omitempty"`
	// Discovery says the build runs feature discovery first, with a review
	// run's budgets.
	Discovery bool `json:"discovery,omitempty"`
}

BuildEstimate is what a build of the repository would do: how many pull requests it places and calls it makes. It has no cost: prices only report.

type BuildOptions

type BuildOptions struct {
	// DryRun estimates and writes nothing.
	DryRun bool
	// Backend and Model, when set, replace the configured ones.
	Backend, Model string
	// Gated refuses a repository whose decisions are not captured (the
	// gate), with Reason ReasonDisabled.
	Gated bool
	// Progress, when set, is told after every batch and stage.
	Progress func(BuildProgress)
	// Taxonomy chooses what a new version is seeded with: TaxonomyAuto
	// (empty) a taxonomy made after the version, else a new discovery;
	// TaxonomyNone nothing; LatestTaxonomy or an id a stored taxonomy.
	Taxonomy string
}

BuildOptions change a first build.

type BuildProgress

type BuildProgress struct {
	Phase string `json:"phase"`
	// Placed counts the pull requests placed or skipped of Total.
	Placed, Total int
	Calls         int
	SpentUSD      float64
	// Unpriced counts the run's calls whose cost is unknown: SpentUSD is
	// then a lower bound.
	Unpriced int
}

BuildProgress is a build's progress.

type BuildReport

type BuildReport struct {
	Repository string `json:"repository"`
	Version    int64  `json:"version"`
	// PullRequests are those this run listed to place.
	PullRequests int `json:"pull_requests"`
	Placed       int `json:"placed"`
	Created      int `json:"created"`
	Skipped      int `json:"skipped"`
	// Failed counts the pull requests whose batch failed down to them alone,
	// LeftOut those that failed once more and were left out.
	Failed   int `json:"failed"`
	LeftOut  int `json:"left_out"`
	Calls    int `json:"calls"`
	Features int `json:"features"`
	// Current says the version became current; otherwise Stopped says why
	// not (a backend reason, or "failed").
	Current         bool    `json:"current"`
	Stopped         string  `json:"stopped,omitempty"`
	SpentUSD        float64 `json:"spent_usd"`
	VersionSpentUSD float64 `json:"version_spent_usd"`
	// UnpricedCalls are the version's calls whose cost is unknown: its spend
	// is then at least VersionSpentUSD.
	UnpricedCalls    int  `json:"unpriced_calls,omitempty"`
	HistoryTruncated bool `json:"history_truncated,omitempty"`
	// Taxonomy is the taxonomy the version was seeded with, 0 for none, and
	// Seeds its features; SeedsJoined are those a pull request joined in
	// this run and SeedsDeleted those deleted before the switch, still
	// empty.
	Taxonomy     int64 `json:"taxonomy,omitempty"`
	Seeds        int   `json:"seeds,omitempty"`
	SeedsJoined  int   `json:"seeds_joined,omitempty"`
	SeedsDeleted int   `json:"seeds_deleted,omitempty"`
	// Discovery is this run's discovery, nil for none; Unseeded says why a
	// new version is built without seeds.
	Discovery *DiscoverReport `json:"discovery,omitempty"`
	Unseeded  string          `json:"unseeded,omitempty"`
}

BuildReport says what Build did; no pull request, feature or reply text.

type Capturer

type Capturer struct {
	Store    *Store
	Registry Registry
	// Config is the config file's; the preferences and the repository's
	// settings are layered on it for every capture.
	Config config.Config
	// Backends builds the backends; zero is generate.DefaultBackendsFor.
	Backends generate.Backends
	// Home holds the agents' session stores; empty is the user's home.
	Home string
	// CursorStore is Cursor's database; empty is its default.
	CursorStore string
	// Extractor is nil for the published prompt's.
	Extractor *decisions.Extractor
	// Prices is nil for pricing.Active.
	Prices func() *pricing.List
	Now    func() time.Time
	// Changed is called once a capture's call is recorded as pending.
	Changed func()
	Log     *slog.Logger
	// CallTimeout is zero for the package's CallTimeout.
	CallTimeout time.Duration
	// contains filtered or unexported fields
}

Capturer captures one session at a time (README.md, "Capture").

func (*Capturer) Capture

func (c *Capturer) Capture(ctx context.Context, t Target, o Options) (Report, error)

Capture runs the pipeline for one session. An error is one the store or the registry returned, or a failed call; what was skipped is in the report.

type ConsolidateReport

type ConsolidateReport struct {
	Repository string `json:"repository"`
	Outcome    string `json:"outcome"`
	// Stopped is the reason a call was not made (a backend or price
	// reason); what was called is still written.
	Stopped  string `json:"stopped,omitempty"`
	Features int    `json:"features"`
	// Merged counts the features merged into another, HeldMerges those the
	// split hold kept apart and InvalidMerges the reply's groups dropped or
	// cut.
	Merged        int `json:"merged"`
	HeldMerges    int `json:"held_merges"`
	InvalidMerges int `json:"invalid_merges"`
	// Split counts the features split, NewFeatures the parts they made,
	// KeptWhole those the reply kept whole or too large to offer and
	// InvalidSplits those whose reply was invalid or whose call failed.
	Split         int `json:"split"`
	NewFeatures   int `json:"new_features"`
	KeptWhole     int `json:"kept_whole"`
	InvalidSplits int `json:"invalid_splits"`
	// DiscardedMerges counts the merge reply's groups not written because a
	// split call failed or was refused.
	DiscardedMerges int `json:"discarded_merges,omitempty"`
	// Changed counts the merges and splits not written because their
	// features changed after they were read.
	Changed int     `json:"changed"`
	Spent   float64 `json:"spent_usd"`
	Error   string  `json:"error,omitempty"`
}

ConsolidateReport says what a consolidation pass did; no feature or reply text.

type Decision

type Decision struct {
	decisions.Decision
	Agent      string    `json:"agent"`
	SessionID  string    `json:"session_id"`
	Repository string    `json:"repository"`
	Branch     string    `json:"branch,omitempty"`
	DecidedAt  time.Time `json:"decided_at"`
}

Decision is a current decision as the log keeps it.

type DecisionView

type DecisionView struct {
	ID                  string             `json:"decision_id"`
	Title               string             `json:"title,omitempty"`
	Summary             string             `json:"summary"`
	Category            decisions.Category `json:"category"`
	Who                 decisions.Who      `json:"who"`
	Rationale           string             `json:"rationale,omitempty"`
	RejectedAlternative string             `json:"rejected_alternative,omitempty"`
	Superseded          bool               `json:"superseded"`
	Files               []string           `json:"files"`
	// DecidedAt is when the decision was first raised (Decision.DecidedAt).
	DecidedAt time.Time `json:"decided_at"`
	// Provisional marks, for a pull request, a decision dated after its
	// saved review was generated: the review has not seen it.
	Provisional bool `json:"provisional,omitempty"`
}

DecisionView is what the app is told of a current decision: no evidence quote, turns or branch.

type DiscoverReport

type DiscoverReport struct {
	Repository string `json:"repository"`
	// TaxonomyID is the stored taxonomy, 0 when none was made.
	TaxonomyID int64  `json:"taxonomy_id,omitempty"`
	Revision   string `json:"revision,omitempty"`
	Features   int    `json:"features"`
	Backend    string `json:"backend,omitempty"`
	Model      string `json:"model,omitempty"`
	// MaxTokens and MaxCalls are the run's budgets; zero is no limit.
	MaxTokens int `json:"max_tokens,omitempty"`
	MaxCalls  int `json:"max_calls,omitempty"`
	Calls     int `json:"calls"`
	// SpentUSD is nil when the run's spend cannot be priced.
	SpentUSD *float64 `json:"spent_usd"`
	// Stopped is why no taxonomy was made.
	Stopped string `json:"stopped,omitempty"`
}

DiscoverReport says what Discover did; no taxonomy text.

type Effective

type Effective struct {
	// Backend is the data extraction backend, resolved as a review's for
	// auto, empty when none passes its check.
	Backend string `json:"backend"`
	// Undetermined is a backend whose checks did not answer in time, apart
	// from none being usable.
	Undetermined bool `json:"backend_undetermined"`
	// Model is the decision model; empty without a backend.
	Model string `json:"effective_model"`
	// Ignored is a chosen model (preferences.decisions.model) that capture
	// does not use with Backend.
	Ignored bool `json:"model_ignored"`
}

Effective is the backend and model decision capture uses.

func EffectiveModel

func EffectiveModel(ctx context.Context, file config.Config, prefs repos.Preferences, backends generate.Backends, list *pricing.List) Effective

EffectiveModel resolves the backend and decision model as a capture does (resolveModel), from the config file with the app's preferences over it. Backends zero builds them from the configuration and list nil is pricing.Active. With auto it runs the backends' checks, for at most effectiveTimeout. No usable backend leaves the Effective empty; checks that do not answer in time leave it empty but Undetermined. Either way the model is unknown and nothing is ignored.

type Extraction

type Extraction struct {
	Meta
	ContentHash, PrefixHash string
	PromptVersion           string
	Backend, Model          string
	// Decisions all have IDs (decisions.Identified).
	Decisions []decisions.Decision
	Dropped   []decisions.Dropped
	Usage     decisions.Usage
	// Cost is nil when unknown.
	Cost *float64
	// TurnTimes are the times of the root session's turns, by ordinal.
	TurnTimes map[int]time.Time
}

Extraction is one extraction to save as the session's head.

type FeatureOptions

type FeatureOptions struct {
	// DryRun renders placement's calls without making them, and writes
	// nothing.
	DryRun bool
	// Limit bounds the items or features taken; zero is the stage's limit.
	Limit int
	// Manual ignores the activation and the text stage's debounce.
	Manual bool
	// Backend and Model, when set, replace the configured ones.
	Backend, Model string
	// contains filtered or unexported fields
}

FeatureOptions change one run of placement or the text stage, for testing by hand.

type FeatureRegistry

type FeatureRegistry interface {
	Preferences(context.Context) (repos.Preferences, error)
	Repositories(context.Context) ([]repos.Repository, error)
	RepositorySettings(ctx context.Context, repository string) (repos.Settings, bool, error)
}

FeatureRegistry is what the feature worker reads of the repository registry; *repos.Registry is one.

type FeatureWorker

type FeatureWorker struct {
	Store    *Store
	Registry FeatureRegistry
	// GitHub opens a REST reader for a repository.
	GitHub func(context.Context, github.Repository) (github.Reader, error)
	// Sessions narrows the session stores linked; zero reads the defaults.
	Sessions sessions.Options
	// Config, Backends and Prices make the placement and text calls as
	// capture's (Capturer).
	Config   config.Config
	Backends generate.Backends
	Prices   func() *pricing.List
	Now      func() time.Time
	// Tick is how often the stages run; zero is FeatureTick.
	Tick time.Duration
	// Window is how far back the first sync lists pull requests; zero is
	// FeatureFirstSyncWindow.
	Window time.Duration
	// Changed is called after a repository's pass that may have changed
	// its pull requests, their sessions or its features.
	Changed func()
	Log     *slog.Logger
	// contains filtered or unexported fields
}

FeatureWorker runs the feature log's stages for every repository whose decisions are captured (README.md, "Feature log"). Run it once in the daemon, beside the capture scheduler and on the same Store.

func (*FeatureWorker) Assign

func (w *FeatureWorker) Assign(ctx context.Context, repository, dir string, o FeatureOptions) (AssignReport, error)

Assign places the repository's pending pull requests and sessions, oldest trigger first, at most FeatureAssignRunLimit (or o.Limit) of them. A backend reason stops it until the next run.

func (*FeatureWorker) Build

func (w *FeatureWorker) Build(ctx context.Context, repository, dir string, o BuildOptions) (BuildReport, error)

Build builds the repository's features from its pull request history in its building version, made or resumed, and makes that version current (README.md, "First build"). It stops, leaving the version building, when a call cannot be made or on a store error; running it again resumes the version. A dry run reports the estimate in Stopped's place and writes nothing.

func (*FeatureWorker) Consolidate

func (w *FeatureWorker) Consolidate(ctx context.Context, repository, dir string, o FeatureOptions) (ConsolidateReport, error)

Consolidate makes a consolidation pass over the repository's features when one is due: at most every FeatureConsolidateEvery, and only when the features changed since the last pass (any time with o.Manual).

func (*FeatureWorker) Discover

func (w *FeatureWorker) Discover(ctx context.Context, repository, dir string) (DiscoverReport, error)

Discover runs feature discovery of the repository once, from the clone in dir, and stores the taxonomy it replies with. It runs on the data extraction backend and model, after listing the whole history and reading its paths as a first build does.

func (*FeatureWorker) EstimateBuild

func (w *FeatureWorker) EstimateBuild(ctx context.Context, repository, dir string, o BuildOptions) (BuildEstimate, error)

EstimateBuild estimates a build of the repository: what is left of its building version, or a new one.

func (*FeatureWorker) Index

func (w *FeatureWorker) Index(ctx context.Context, repository, dir string, fresh []gitlocal.Commit, full bool) (IndexReport, error)

Index links commits to the sessions on this machine and replaces their links: the fresh commits Sync recorded within FeatureLookback; every FeatureQuiet, the commits within the session window of now, which sessions still running may yet link to; and every FeatureRelinkEvery, or with full, every commit within FeatureLookback.

func (*FeatureWorker) ListHistory

func (w *FeatureWorker) ListHistory(ctx context.Context, repository string) (HistoryReport, error)

ListHistory lists every pull request of the repository once, for its first build, and saves them without moving the sync's ETag or cursor. A repository whose history was listed before is not listed again.

func (*FeatureWorker) PlanBuild

func (w *FeatureWorker) PlanBuild(ctx context.Context, repository, dir string, o BuildOptions) (backend, model, reason string, err error)

PlanBuild resolves the backend and model a build of the repository would call, without listing its history, or the reason it cannot run now (the gate, or a backend reason). Nothing about the price stops a build.

func (*FeatureWorker) Run

func (w *FeatureWorker) Run(ctx context.Context)

Run runs the stages every Tick and when woken, until ctx ends. It returns once the stage in progress has.

func (*FeatureWorker) RunOnce

func (w *FeatureWorker) RunOnce(ctx context.Context)

RunOnce runs the stages (sync, index, placement, text and consolidation) for every repository whose decisions are captured and that has a clone. A repository turned off is not read and makes no GitHub call.

func (*FeatureWorker) Summarize

func (w *FeatureWorker) Summarize(ctx context.Context, repository, dir string, o FeatureOptions) (SummaryReport, error)

Summarize writes the text of the repository's features whose members changed and have been unchanged for FeatureQuiet (not with o.Manual), longest behind first, at most FeatureSummaryRunLimit (or o.Limit). A backend reason stops it until the next run.

func (*FeatureWorker) Sync

func (w *FeatureWorker) Sync(ctx context.Context, repository, dir string) (SyncReport, error)

Sync lists the repository's pull requests updated since the last sync (the first lists those of Window) and reads the paths and commits of each whose head changed from the clone in dir, fetching missing heads.

func (*FeatureWorker) Wake

func (w *FeatureWorker) Wake()

Wake runs the stages soon, for a settings change. A nil worker ignores it.

type FlowPull

type FlowPull struct {
	Author    string
	Draft     bool
	CreatedAt time.Time
	MergedAt  *time.Time
}

FlowPull is one pull request for Analytics' pull request flow: who opened it, when, and when it merged.

type HistoryReport

type HistoryReport struct {
	Repository string `json:"repository"`
	// Listed is false when the history was listed before.
	Listed       bool `json:"listed"`
	Calls        int  `json:"github_calls"`
	PullRequests int  `json:"pull_requests_listed"`
	// Truncated is a listing that stopped at its bound: the least recently
	// updated pull requests are missing. The listing is not retried.
	Truncated bool `json:"truncated,omitempty"`
}

HistoryReport says what ListHistory did.

type IndexReport

type IndexReport struct {
	Repository string `json:"repository"`
	Full       bool   `json:"full"`
	Commits    int    `json:"commits_linked"`
	Links      int    `json:"links"`
	Sessions   int    `json:"sessions"`
}

IndexReport says what Index did.

type LedgerEntry

type LedgerEntry struct {
	Repository, Backend string
	State, Reason       string
	StartedAt           time.Time
	// Cost is nil when unknown or, for a pending attempt, not known yet.
	Cost *float64
}

LedgerEntry is one attempt of the ledger, as analytics counts it.

type LinkedSessions

type LinkedSessions func(ctx context.Context, repository string, number int) ([]locallink.Session, time.Time, error)

LinkedSessions lists the sessions the pull request's latest saved review links on this machine (as its PR page's sessions list does), and when that review was generated, zero without one.

type Meta

type Meta struct {
	Agent, ID          string
	Repository, Branch string
	Path               string
	Fingerprint        string
}

Meta is where a session's transcript is and what it belongs to.

type OpenOptions

type OpenOptions struct {
	// AbandonPending settles the attempts a previous run left pending as
	// abandoned. Only the daemon, the one writer that makes calls unattended,
	// sets it.
	AbandonPending bool
	// ReadOnly opens an existing store without writing to it, and an empty
	// in-memory one when there is none, for a dry run.
	ReadOnly bool
}

OpenOptions choose how Open opens the store.

type Options

type Options struct {
	// DryRun stops before the call and writes nothing.
	DryRun bool
	// Force skips the gate, the throttle and the unchanged check.
	Force bool
	// Backend and Model, when set, replace the configured ones.
	Backend, Model string
	// DebugDir, when set, receives the rendered transcript (transcript.txt),
	// the user message sent (user.txt) and the raw reply (reply.json), for
	// testing by hand. Each capture first removes the previous capture's
	// files. The daemon never sets it.
	DebugDir string
}

Options change one capture.

type Person

type Person struct {
	Email     string  `json:"email"`
	Name      *string `json:"name"`
	Login     *string `json:"login"`
	AvatarURL *string `json:"avatar_url"`
	// contains filtered or unexported fields
}

Person is who a session or pull request belongs to. The avatar is never read: the app draws initials.

type PlacedItem

type PlacedItem struct {
	Kind    string `json:"kind"`
	Trigger string `json:"trigger"`
	Number  int    `json:"number,omitempty"`
	Title   string `json:"title,omitempty"`
	Session string `json:"session,omitempty"`
	Outcome string `json:"outcome"`
	Reason  string `json:"reason,omitempty"`
	Feature string `json:"feature,omitempty"`
	// FeatureTitle is a new feature's provisional title.
	FeatureTitle string `json:"feature_title,omitempty"`
	// Candidates are the candidate features' titles, in the order offered.
	Candidates []string `json:"candidates"`
	InputBytes int      `json:"input_bytes"`
	Cost       *float64 `json:"cost_usd,omitempty"`
	Error      string   `json:"error,omitempty"`
}

PlacedItem is one item Assign took.

type PullCommit

type PullCommit struct {
	SHA         string
	CommittedAt time.Time
}

PullCommit is one commit of a pull request.

type PullRequestDecisions

type PullRequestDecisions struct {
	Sessions []PullRequestSession `json:"sessions"`
	// ReviewGeneratedAt is when the saved review the provisional marks are
	// relative to was generated; null without one.
	ReviewGeneratedAt *time.Time `json:"review_generated_at"`
}

PullRequestDecisions is the answer for a pull request.

type PullRequestSession

type PullRequestSession struct {
	SessionDecisions
	// Title is the linked session's; a live one's is in the live list.
	Title string `json:"title,omitempty"`
	// Live is a session the tracker follows that is not over yet.
	Live bool `json:"live"`
}

PullRequestSession is a session of a pull request with its decisions.

type PullSync

type PullSync struct {
	ETag string
	// Cursor is the latest updated_at listed, zero before the first sync.
	Cursor time.Time
	// ActivatedAt is when the feature log first ran for the repository;
	// earlier work is not assigned.
	ActivatedAt time.Time
	// RelinkedAt is the last full re-link, zero for none.
	RelinkedAt time.Time
	// HistoryListedAt is when a first build listed the whole history, zero
	// before.
	HistoryListedAt time.Time
}

PullSync is a repository's pull request sync state.

type Registry

type Registry interface {
	Preferences(context.Context) (repos.Preferences, error)
	RepositorySettings(ctx context.Context, repository string) (repos.Settings, bool, error)
}

Registry is what capture reads of the repository registry.

type Report

type Report struct {
	Outcome string `json:"outcome"`
	Reason  string `json:"reason,omitempty"`
	// Mode is "full" or "extend".
	Mode    string `json:"mode,omitempty"`
	Backend string `json:"backend,omitempty"`
	Model   string `json:"model,omitempty"`
	Turns   int    `json:"turns"`
	// Tokens is the estimated prompt, MaxTokens the bound reserved for.
	Tokens    int64    `json:"estimated_tokens"`
	MaxTokens int64    `json:"max_tokens"`
	Reserved  float64  `json:"reserved_usd"`
	Cost      *float64 `json:"cost_usd,omitempty"`
	Decisions int      `json:"decisions"`
}

Report is what a capture did.

type ResolvedFeature

type ResolvedFeature struct {
	// ID is the feature, followed to the one it was merged into.
	ID         string `json:"id"`
	Repository string `json:"repository"`
	// Archived is a feature of a retired version.
	Archived bool `json:"archived,omitempty"`
}

ResolvedFeature is what a link to a feature shows.

type SampleDecisionRef

type SampleDecisionRef struct {
	ID, Agent, Session string
	Turn               int
}

SampleDecisionRef addresses one saved decision of a session's turn.

type SampleFeature

type SampleFeature struct {
	ID, Title, Description, Digest string
	// KeyDecisions name the decisions the text cites, which Save stored.
	KeyDecisions []SampleDecisionRef
	Pulls        []int
	// Sessions are Claude Code sessions placed on the feature on their own.
	Sessions []string
	Updated  time.Time
}

SampleFeature is a feature of the tour's sample project as the feature log would leave it: its text written, its pull requests and the sessions placed on it.

type Scheduler

type Scheduler struct {
	Tracker Tracker
	Source  Source
	Worker  Worker
	Now     func() time.Time
	// Tick is how often the triggers are evaluated; zero is TickEvery.
	Tick time.Duration
	// Changed is called after a capture that saved decisions, recorded a
	// skip or settled an attempt.
	Changed func()
	Log     *slog.Logger
	// contains filtered or unexported fields
}

Scheduler decides when the tracked sessions are captured and hands them, one at a time, to the worker. Run it once in the daemon.

func (*Scheduler) Enqueue

func (s *Scheduler) Enqueue(agent, id string)

Enqueue asks for the session to be captured; a session already waiting is not queued twice.

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context)

Run evaluates the triggers every Tick, queues a session that is about to compact at once, and captures the queued sessions one at a time, until ctx ends. It returns once the capture in progress has.

type Session

type Session struct {
	Agent, ID                string
	Repository, Branch, Path string
	Fingerprint              string
	ContentHash, PrefixHash  string
	PromptVersion            string
	Backend, Model           string
	// Decisions are the head extraction's, with IDs; nil when none was made.
	Decisions  []decisions.Decision
	Coverage   string
	SkipReason string
	Failures   int
	UpdatedAt  time.Time
}

Session is a captured session's row: its coverage and head extraction.

type SessionCommit

type SessionCommit struct {
	Agent, SessionID, SHA string
	Method                string
	Confidence            float64
}

SessionCommit is one session linked to a commit.

type SessionDecisions

type SessionDecisions struct {
	Agent     string `json:"agent"`
	SessionID string `json:"session_id"`
	// Repository is the session's, as last captured; empty for a session
	// never captured. The gate reads it; the app is not told.
	Repository string `json:"-"`
	// CaptureOff says why capture does not run for the session now
	// (API.Registry; CaptureOff* values), empty when it does or is not known.
	CaptureOff string `json:"capture_off,omitempty"`
	// Coverage is CoverageExtracted, CoverageSkipped, or empty for a session
	// never captured.
	Coverage   string     `json:"coverage"`
	SkipReason string     `json:"skip_reason,omitempty"`
	UpdatedAt  *time.Time `json:"updated_at"`
	// Capturing is a model call for the session that has not settled.
	Capturing bool           `json:"capturing"`
	Decisions []DecisionView `json:"decisions"`
}

SessionDecisions is what the app shows of one session's capture: its current decisions, its coverage and why it was skipped, when its row last changed and whether a call for it is in flight.

type SessionKey

type SessionKey struct{ Agent, ID string }

SessionKey names a session.

type Source

type Source interface {
	SubscribeCounted(buffer int, match func(hookevents.Event) bool) (events <-chan hookevents.Record, cancel func(), dropped func() uint64)
}

Source delivers hook events; *hookevents.Store is one.

type Stats

type Stats struct {
	// Count is the session's attempts since the day's start; LastStart is its
	// latest attempt and LastFail its latest failed one, zero for none.
	Count     int
	LastStart time.Time
	LastFail  time.Time
}

Stats are what the throttle reads from the ledger.

type Store

type Store struct {

	// Changed, when set, is called after each write that changes what the
	// app shows: a session's coverage or decisions, or an attempt beginning
	// or settling. The daemon publishes the app's decisions event with it.
	Changed func()
	// contains filtered or unexported fields
}

Store keeps the sessions' coverage and head extraction, the decision log and the ledger of model calls.

func Open

func Open(path string, o OpenOptions) (*Store, error)

Open creates or opens the private store at path, in a 0700 directory.

func (*Store) Activate

func (s *Store) Activate(ctx context.Context, repository string) (PullSync, error)

Activate reads the repository's sync state, activating the repository the first time.

func (*Store) Begin

func (s *Store) Begin(ctx context.Context, a Attempt) (int64, error)

Begin records an attempt as pending before its call, with its reservation.

func (*Store) Close

func (s *Store) Close() error

Close closes the database.

func (*Store) Coverage

func (s *Store) Coverage(ctx context.Context, sessions ...SessionKey) (map[SessionKey][2]string, error)

Coverage reads each session's coverage and skip reason; a session without a row is left out.

func (*Store) Covered

func (s *Store) Covered(ctx context.Context, m Meta) error

Covered records that the head extraction still covers the session as fingerprinted (a cache hit): its rendered transcript did not change.

func (*Store) Current

func (s *Store) Current(ctx context.Context, sessions ...SessionKey) ([]Decision, error)

Current reads the current decisions of the sessions, in session and extraction order, with their files.

func (*Store) Failed

func (s *Store) Failed(ctx context.Context, m Meta) (int, error)

Failed counts a failed extraction of the session and returns how many followed its last success.

func (*Store) Ledger

func (s *Store) Ledger(ctx context.Context, since time.Time) ([]LedgerEntry, error)

Ledger reads the capture attempts started at or after since, oldest first.

func (*Store) LiveVersion

func (s *Store) LiveVersion(ctx context.Context, repository string) (int64, error)

LiveVersion is the repository's feature version in use, created when it has none.

func (*Store) PathsFailed

func (s *Store) PathsFailed(ctx context.Context, repository string, number int, head string) (bool, error)

PathsFailed counts a failed read of the pull request's commits at head (a new head or base starts the count again, SavePulls). It is tried again after FeatureQuiet; after FeaturePathTries failures its paths are unknown at that head, and true is returned.

func (*Store) PullFlow

func (s *Store) PullFlow(ctx context.Context, repository string, since time.Time) ([]FlowPull, bool, error)

PullFlow lists the repository's pull requests not opened by a bot that were opened or merged at or after since, and whether the feature log has listed the repository yet: one it never listed has none to give. The repository is named as the feature log stores it, so the primary key finds its rows.

func (*Store) PullsWithCommitsSince

func (s *Store) PullsWithCommitsSince(ctx context.Context, repository string, since time.Time) ([]StoredPull, error)

PullsWithCommitsSince lists the pull requests holding a recorded commit made at or after since, at the head their commits were recorded at, so they are linked again while a newer head cannot be read. The base is the current one. A new base with the same head has the pull request read again (SavePulls); with a new head too, the recorded head is kept until the new one is read.

func (*Store) PullsWithoutPaths

func (s *Store) PullsWithoutPaths(ctx context.Context, repository string) ([]StoredPull, error)

PullsWithoutPaths lists the repository's pull requests that may be placed in a feature, open or merged and not opened by a bot, whose paths were not read at their head and are due to be, newest first.

func (*Store) ReplaceSessionCommits

func (s *Store) ReplaceSessionCommits(ctx context.Context, repository string, shas []string, links []SessionCommit, relinked time.Time) error

ReplaceSessionCommits makes links the sessions linked to the commits shas: every earlier link of those commits goes. relinked, when not zero, records a full re-link. A commit whose sessions changed marks the features holding a pull request with it and the features of its old and new sessions.

func (*Store) ResolveFeature

func (s *Store) ResolveFeature(ctx context.Context, id string) (ResolvedFeature, error)

ResolveFeature is the feature a link to id shows: followed to the feature it was merged into, archived when its version was retired. sql.ErrNoRows for an unknown feature or one of a version being built.

func (*Store) RetryUnknownPaths

func (s *Store) RetryUnknownPaths(ctx context.Context, repository string) error

RetryUnknownPaths has every pull request whose commits failed to be read read again now, those with unknown paths FeaturePathTries more times.

func (*Store) Save

func (s *Store) Save(ctx context.Context, e Extraction) error

Save makes the extraction the session's head and its decisions the session's current ones, as the hosted decision log does: a decision is upserted by its ID, so it keeps the row and creation time it had; the session's current decisions missing from the new set become obsolete and are never deleted. A decision is dated by its first cited turn's time, else the date its ID had, else the time of the extraction.

func (*Store) SaveHistoryPulls

func (s *Store) SaveHistoryPulls(ctx context.Context, repository string, pulls []github.ListedPull) error

SaveHistoryPulls upserts a listing of the repository's whole history as SavePulls does, leaving the sync's ETag and cursor as they are, and records that the history was listed. A pull request stored with a later update, as a sync during the listing saves it, is kept as it is.

func (*Store) SavePulls

func (s *Store) SavePulls(ctx context.Context, repository string, pulls []github.ListedPull, etag string, cursor time.Time) error

SavePulls upserts the listed pull requests, marking the feature of one whose title, description or state changed, and, for a non-zero cursor, moves the sync state to etag and the cursor; the cursor never goes back. A new head or base starts the count of failed reads again, and a new base has the pull request's commits read again.

func (*Store) SeedSample

func (s *Store) SeedSample(ctx context.Context, repository string, pulls []github.ListedPull, summaries map[int]string, features []SampleFeature) error

SeedSample writes the sample project's pull requests, their summaries and its features into a store no feature build has run on. Nothing else writes features without a model; this is for the sample project alone.

func (*Store) Session

func (s *Store) Session(ctx context.Context, agent, id string) (Session, bool, error)

Session reads the session's row; false when it has none.

func (s *Store) SessionLinks(ctx context.Context, repository, agent, sessionID string) (features.SessionLinks, error)

SessionLinks reads what attributes a captured session's decisions in the repository's live version: the feature pull requests its linked commits are in, and the feature it is in on its own. A session without a capture row has none.

func (*Store) Sessions

func (s *Store) Sessions(ctx context.Context, keys ...SessionKey) ([]SessionDecisions, error)

Sessions reads what the app shows of each session, in the given order.

func (*Store) SetPath

func (s *Store) SetPath(ctx context.Context, agent, id, path string) error

SetPath records where the session's transcript is.

func (*Store) SetPullPaths

func (s *Store) SetPullPaths(ctx context.Context, repository string, number int, head string, paths []string, commits []PullCommit) error

SetPullPaths records what the pull request touches at head: its paths, nil when they cannot be read, and its commits, which replace those it had.

func (*Store) Settle

func (s *Store) Settle(ctx context.Context, id int64, state, reason string, cost *float64, usage decisions.Usage) error

Settle records how an attempt ended. A nil cost is unknown.

func (*Store) Skip

func (s *Store) Skip(ctx context.Context, m Meta, reason string) error

Skip records that the session, as fingerprinted, was not extracted and why. Its head extraction and decisions stay as they were.

func (*Store) Stats

func (s *Store) Stats(ctx context.Context, agent, id string, day time.Time) (Stats, error)

Stats reads the ledger for the session since day.

func (*Store) Taxonomy

func (s *Store) Taxonomy(ctx context.Context, repository, ref string) (int64, discover.FeatureTaxonomy, error)

Taxonomy reads the repository's taxonomy named by ref, its id or LatestTaxonomy, with its id.

type StoredPull

type StoredPull struct {
	Number                    int
	HeadSHA, BaseSHA, BaseRef string
}

StoredPull is a pull request whose paths are to be read.

type SummarizedFeature

type SummarizedFeature struct {
	Feature string `json:"feature"`
	Title   string `json:"title"`
	Outcome string `json:"outcome"`
	Reason  string `json:"reason,omitempty"`
	// PullRequests are its pull requests' numbers, earliest opened first.
	PullRequests []int `json:"pull_requests"`
	// PullSummaries counts the generated pull request summaries made by a
	// call, PullSummariesReused those kept.
	PullSummaries       int      `json:"pull_request_summaries"`
	PullSummariesReused int      `json:"pull_request_summaries_reused"`
	Decisions           int      `json:"decisions"`
	KeyDecisions        int      `json:"key_decisions"`
	InputBytes          int      `json:"input_bytes"`
	Cost                *float64 `json:"cost_usd,omitempty"`
	Error               string   `json:"error,omitempty"`
}

SummarizedFeature is one feature Summarize took.

type SummaryReport

type SummaryReport struct {
	Repository string              `json:"repository"`
	Features   []SummarizedFeature `json:"features"`
	// Stopped is the reason no further call was made (a backend or price
	// reason).
	Stopped string  `json:"stopped,omitempty"`
	Spent   float64 `json:"spent_usd"`
}

SummaryReport says what Summarize did.

type SyncReport

type SyncReport struct {
	Repository  string `json:"repository"`
	Calls       int    `json:"github_calls"`
	NotModified bool   `json:"not_modified"`
	Listed      int    `json:"pull_requests_listed"`
	// Truncated is a listing that stopped at its bound: older pull requests
	// still due were skipped.
	Truncated bool `json:"truncated,omitempty"`
	PathsRead int  `json:"paths_read"`
	// PathsFailed are pull requests whose commits failed to be read, to be
	// tried again; PathsUnknown those of them that failed FeaturePathTries
	// times at their head and now have unknown paths.
	PathsFailed  int `json:"paths_failed"`
	PathsUnknown int `json:"paths_unknown"`
	// Stopped is set when the repository was turned off during the sync.
	Stopped bool `json:"stopped,omitempty"`
	Commits int  `json:"commits_recorded"`
	// contains filtered or unexported fields
}

SyncReport says what Sync did.

type Target

type Target struct {
	Agent, ID string
	// Path is the transcript path the session's hooks named, if any.
	Path               string
	Repository, Branch string
	// Ended is a session that sent its end event: the daily attempt limit
	// does not hold it back.
	Ended bool
}

Target is a session to capture.

type Timeline

type Timeline struct {
	Store    *Store
	Registry TimelineRegistry
	// Clone opens the repository's clone for a request; nil, or a nil
	// answer, reads nobody and no review.
	Clone func(ctx context.Context, repository string) TimelineClone
	// Login is the user's GitHub login, "" when unknown; it folds the pull
	// requests they opened into them.
	Login func(ctx context.Context) string
	// Home holds the agents' session stores, whose transcripts a decision's
	// cited prompts are read from; empty is the user's home.
	Home string
	// CursorStore is Cursor's database; empty is its default.
	CursorStore string
	// Transcripts serves a captured session's transcript, tracked or ended;
	// nil serves none.
	Transcripts *livesessions.Transcripts
	// contains filtered or unexported fields
}

Timeline serves /api/timeline/.

func (*Timeline) Handler

func (t *Timeline) Handler() http.Handler

Handler serves the Timeline API. Every route names, or reads from, a picked repository whose decisions are not turned off; another is a 400 for a list and a 404 for a detail.

type TimelineClone

type TimelineClone interface {
	// Person is who sessions in this clone belong to, by its git
	// user.email and user.name; nil when it has no address.
	Person(ctx context.Context) *Person
	// Review is the summary of the pull request's latest saved review and
	// its state against head ("current" or "outdated"), "" and "none"
	// without one.
	Review(ctx context.Context, number int, head string) (summary, state string)
}

TimelineClone is what one request reads of a repository's clone; it reads each once.

type TimelineRegistry

type TimelineRegistry interface {
	Preferences(context.Context) (repos.Preferences, error)
	Repositories(context.Context) ([]repos.Repository, error)
}

TimelineRegistry is what the Timeline reads of the repository registry; *repos.Registry is one.

type Tracker

type Tracker interface {
	Sessions() []livesessions.Session
	Lookup(agent, id string) (livesessions.Session, string, bool)
}

Tracker is what the scheduler reads of the live sessions; *livesessions.Tracker is one.

type Worker

type Worker interface {
	Capture(context.Context, Target, Options) (Report, error)
}

Worker captures one session; *Capturer is one.

Jump to

Keyboard shortcuts

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