@agentionai/fieldwork-cli 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -17
- package/dist/changelog.js +137 -0
- package/dist/client.js +80 -7
- package/dist/credentials.js +78 -0
- package/dist/fieldwork-skill.md +77 -19
- package/dist/main.js +467 -83
- package/dist/select.js +124 -0
- package/dist/workspace.js +71 -21
- package/package.json +27 -7
package/README.md
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
Requires Node.js **22+** and npm. Version **0.
|
|
7
|
+
Requires Node.js **22+** and npm. Version **0.7.0** is prepared for publication; until published, install the supplied archive:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install --global ./agentionai-fieldwork-cli-0.
|
|
10
|
+
npm install --global ./agentionai-fieldwork-cli-0.7.0.tgz
|
|
11
11
|
fieldwork --version
|
|
12
12
|
fieldwork --help
|
|
13
13
|
```
|
|
@@ -15,20 +15,23 @@ fieldwork --help
|
|
|
15
15
|
After publication:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
npm install --global @agentionai/fieldwork-cli@0.
|
|
18
|
+
npm install --global @agentionai/fieldwork-cli@0.7.0
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Pin the version. The npm registry currently holds 0.3.0 and 0.4.0, which predate credentials and paged responses and cannot use the hosted service, so an unpinned install gets a client that fails against it.
|
|
22
|
+
|
|
23
|
+
A checkout and pnpm are not required to use the installed CLI. npm resolves Commander, its only runtime dependency. This package includes neither the server nor the web app and installs no services. By default it connects to the hosted service at `https://app.fieldworkledger.com`, which requires a credential; a local server is named with `--url` or `FIELDWORK_URL`. Installing a new client does not update server behavior.
|
|
22
24
|
|
|
23
25
|
## Quick start
|
|
24
26
|
|
|
25
|
-
Use actual
|
|
27
|
+
Issue an agent credential from the web app's Account page, then store it once for this machine. Use actual campaign references; the example campaign must already exist:
|
|
26
28
|
|
|
27
29
|
```sh
|
|
28
|
-
fieldwork
|
|
30
|
+
fieldwork auth login --token TOKEN
|
|
31
|
+
fieldwork campaigns list
|
|
29
32
|
mkdir memory-study
|
|
30
33
|
cd memory-study
|
|
31
|
-
fieldwork
|
|
34
|
+
fieldwork setup campaign --campaign memory-study
|
|
32
35
|
fieldwork context
|
|
33
36
|
fieldwork experiments create --json '{"name":"Baseline","hypothesis":"The candidate meets the agreed target"}'
|
|
34
37
|
fieldwork runs create --experiment baseline --json '{"title":"Attempt 1"}'
|
|
@@ -41,21 +44,26 @@ Creating a missing campaign is explicit: `setup campaign --campaign new-study --
|
|
|
41
44
|
|
|
42
45
|
Run `fieldwork <group> <command> --help` for flags and payload requirements.
|
|
43
46
|
|
|
44
|
-
| Command
|
|
45
|
-
|
|
|
46
|
-
| `products`
|
|
47
|
-
| `campaigns`
|
|
48
|
-
| `experiments
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
47
|
+
| Command | Operations |
|
|
48
|
+
| ---------------- | ---------------------------------------------------------------------- |
|
|
49
|
+
| `products` | list, get, create, update, delete |
|
|
50
|
+
| `campaigns` | list, get, context, create, update, delete |
|
|
51
|
+
| `experiments` | list, get, context, create, update, delete |
|
|
52
|
+
| `runs` | list, get, context, create, record, update, delete |
|
|
53
|
+
| `artifacts` | list, get, create, update, delete, diff |
|
|
54
|
+
| `schemas` | list, publish, default, set-default, get, template, validate, extend |
|
|
55
|
+
| `charts` | fields, list, create, get, data, series, frontier, delete |
|
|
51
56
|
| `setup campaign` | Attach a directory to a campaign; optionally create a missing campaign |
|
|
52
|
-
| `context`
|
|
57
|
+
| `context` | Effective server, campaign scope, and live inherited context |
|
|
58
|
+
| `auth` | login, status, logout: the credential stored for this server |
|
|
59
|
+
| `whoami` | The credential's organization and current permissions |
|
|
60
|
+
| `changelog` | Recent releases of this CLI, offline |
|
|
53
61
|
|
|
54
62
|
- References accept stubs or UUIDs. Experiments/runs use campaign scope (`--campaign REF` or workspace); JSON `...Id` fields, schema versions, and charts require IDs.
|
|
55
63
|
- Campaign list/create do not inherit product scope: pass `--product REF` when needed.
|
|
56
64
|
- Schemas can belong to products, campaigns, or experiments. `schemas list --inherited` includes ancestors; defaults affect new records, not existing pins. Templates are illustrative, never measured evidence.
|
|
57
65
|
- `schemas validate VERSION_ID --ready --json -` validates without saving. Invalid reports go to stdout, with `VALIDATION_FAILED` on stderr and exit 1; valid reports exit 0 even with missing-observation warnings. `--ready` checks required execution fields, not experiment variations.
|
|
58
|
-
- Charts are immutable definitions over live typed successful-run data, with explicit exclusions and source run revisions. Discover fields before creation. Optional typed aggregation supports mean/sample stdev with mean±SD whiskers or line bands; no arbitrary expressions or cross-version merging.
|
|
66
|
+
- Charts are immutable definitions over live typed successful-run data, with explicit exclusions and source run revisions. Discover fields before creation. Optional typed aggregation supports mean/sample stdev with mean±SD whiskers or line bands; no arbitrary expressions or cross-version merging. Two choices about a raw chart are saved with it and can change: `charts series CHART_ID` colours it by a typed field or by groups of runs you define, and a scatter chart's `charts frontier CHART_ID --json '{"runIds":[...]}'` saves the runs to join, chosen rather than computed.
|
|
59
67
|
- Create/update read an object with `--json JSON` or stdin using `--json -`. Data results are JSON on stdout; errors are JSON on stderr with nonzero status. Help/version are text and work offline.
|
|
60
68
|
- Updates require the current `revision`. Campaign/experiment/run deletes require `--revision N`; product/chart deletes do not. Re-read on conflicts, never blindly retry a create or increment stale revisions.
|
|
61
69
|
- Only planned runs can be deleted. Parents with child records are protected; experiments with schemas referenced by campaign charts are also protected. Deleting a chart does not delete runs.
|
|
@@ -63,7 +71,7 @@ Run `fieldwork <group> <command> --help` for flags and payload requirements.
|
|
|
63
71
|
|
|
64
72
|
## Workspaces, server selection, and agent skill
|
|
65
73
|
|
|
66
|
-
Server precedence is `--url`, `FIELDWORK_URL`, legacy `LAB_URL`, nearest workspace config, then `http://127.0.0.1:4310
|
|
74
|
+
Server precedence is `--url`, `FIELDWORK_URL`, legacy `LAB_URL`, nearest workspace config, then `https://app.fieldworkledger.com`. A local server needs `--url http://127.0.0.1:4310` (or `FIELDWORK_URL`); a workspace set up against it remembers it. Credentials are stored per server, so a local token is never sent to the hosted service. Use an HTTP(S) origin without credentials, resource paths, queries, or fragments. Product scoping is not authentication.
|
|
67
75
|
|
|
68
76
|
Setup creates `.fieldwork/workspace.json`, `README.md`, `fieldwork-skill.md`, an `AGENTS.md` reference (or appends to existing `agents.md`), and `docs/`, `assets/`, `logs/`, `results/`. Commands find the nearest binding upward from subdirectories. Setup refuses rebinding and preserves existing files. Legacy `.lab/workspace.json` remains supported. No files are uploaded or registered automatically.
|
|
69
77
|
|
|
@@ -85,3 +93,21 @@ MCP, job orchestration, product queries/local search, managed Markdown recovery,
|
|
|
85
93
|
### Recipes and artifacts (0.4.0)
|
|
86
94
|
|
|
87
95
|
`artifacts list --product REF`, `create --product REF --json`, `get/update/delete` with revision checks, and `diff FROM TO --product` are available in 0.4.0. Experiments and runs accept `artifacts: [stub-or-id]` references stored as immutable IDs; first reference permanently freezes the artifact, and changes require a `derivedFrom` successor with a new stub. Included in CLI 0.4.0; requires the updated API.
|
|
96
|
+
|
|
97
|
+
### Selection, one-call recording and typed refs (0.5.0)
|
|
98
|
+
|
|
99
|
+
`runs list` and `experiments list` accept `--where FIELD=VALUE` (also `!=`, `>=`, `<=`, `>`, `<`; repeatable, combined with AND), `--fields PATHS`, `--format tsv`, and `runs list --experiment REF`. They select and project recorded values and compute nothing. **They now exclude superseded records (`extras.superseded_by`) and work under abandoned experiments by default**; pass `--include-superseded` or `--include-abandoned` to see them. This part works against any compatible API.
|
|
100
|
+
|
|
101
|
+
`runs record REF --json JSON` records observations and a status in one call, with the server reading the current revision, and `runs create` accepts `status`, `stub`, `startedAt`, `finishedAt` and `errorSummary`. Schema fields may have `type: ref` (optional `refKind`), validated against the product's artifact registry, linking and freezing the artifact and usable as a chart axis or `groupBy` key. Charts admit other versions of the same schema when every charted field keeps its value kind, unit and direction. These need the updated API.
|
|
102
|
+
|
|
103
|
+
### Credentials (0.6.0)
|
|
104
|
+
|
|
105
|
+
`auth login --token TOKEN` verifies a credential against the server and stores it for that server only, under your config directory and owner-readable; `auth status` shows which credential is in use and `auth logout` removes it. Global `--token` and `--org` (or `FIELDWORK_TOKEN` and `FIELDWORK_ORGANIZATION`) override it per command; an agent credential is bound to one organization and needs no `--org`. `whoami` reports the credential's organization and current permissions. Agent credentials are issued from the web app's Account page. Requires an API with authentication.
|
|
106
|
+
|
|
107
|
+
### Hosted default, paging, chart series and schema backfill (0.7.0)
|
|
108
|
+
|
|
109
|
+
The default server is the hosted service; a local one needs `--url` or `FIELDWORK_URL`. Lists and every stub lookup walk the API's paged `{ items, nextCursor }` responses, and `get`/`context` return child runs as a first page rather than an array. `charts series` and `charts frontier` save a raw chart's colouring and chosen frontier, and `schemas extend` accepts `backfill` to fill in fields a run predates on runs it re-pins, noted in `research.backfilled` and correctable with `runs update`. These need the updated API.
|
|
110
|
+
|
|
111
|
+
### Release history
|
|
112
|
+
|
|
113
|
+
`fieldwork changelog [--since VERSION] [--release VERSION]` prints recent releases offline, marking per change whether it needs an updated API. The package carries recent releases only; the repository's `docs/cli-changelog.md` is the full history. A newer client never upgrades a server, and the CLI cannot verify what an API provides.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export const fullHistory = 'docs/cli-changelog.md in the Fieldwork repository';
|
|
2
|
+
export const entries = [
|
|
3
|
+
{
|
|
4
|
+
version: '0.7.0',
|
|
5
|
+
date: '2026-09-22',
|
|
6
|
+
status: 'prepared',
|
|
7
|
+
changes: [
|
|
8
|
+
{
|
|
9
|
+
summary: 'The default server is the hosted service, https://app.fieldworkledger.com, instead of http://127.0.0.1:4310. A local server needs --url or FIELDWORK_URL; a workspace set up against one keeps using it.',
|
|
10
|
+
requiresApi: false,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
summary: 'Experiment and run lists, and every stub lookup behind get, record, update and delete, walk the paged { items, nextCursor } responses. 0.6.0 read the first page shape as an invalid collection against a current server.',
|
|
14
|
+
requiresApi: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
summary: 'charts frontier CHART_ID --json {"runIds":[...]} saves the runs a scatter chart joins as its frontier, replacing any earlier choice. Chosen, never computed; only runs the chart plots are accepted.',
|
|
18
|
+
requiresApi: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
summary: 'charts series CHART_ID --json {"series":...} colours a raw chart by a typed field, or by groups of runs you define (for a split no field records, such as ours against as shipped); null restores comparison-context series. charts create accepts series too.',
|
|
22
|
+
requiresApi: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
summary: 'schemas extend accepts backfill: {field: {RUN_ID: value}} to fill in parameter or context fields a run predates -- added after it was recorded, by this extension or an earlier one -- finished runs included. Runs note them in research.backfilled, and a backfilled value can be corrected with runs update.',
|
|
26
|
+
requiresApi: true,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
version: '0.6.0',
|
|
32
|
+
date: '2026-09-20',
|
|
33
|
+
status: 'prepared',
|
|
34
|
+
changes: [
|
|
35
|
+
{
|
|
36
|
+
summary: 'fieldwork auth login, status and logout store one credential per server under your config directory, owner-readable only. Ordinary commands then need no --token. login verifies the credential before storing it, and status reports which source is in use.',
|
|
37
|
+
requiresApi: false,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
summary: 'Global --token and --org send a credential and an organization with every request. An agent credential is bound to one organization, so --org is unnecessary for one.',
|
|
41
|
+
requiresApi: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
summary: 'Authentication failures explain themselves: 401 reports a missing credential and 403 the wrong organization or a withdrawn grant, both stating that a retry will not help.',
|
|
45
|
+
requiresApi: true,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
version: '0.5.0',
|
|
51
|
+
date: '2026-09-18',
|
|
52
|
+
status: 'prepared',
|
|
53
|
+
changes: [
|
|
54
|
+
{
|
|
55
|
+
summary: 'runs list and experiments list select and project rows: --where FIELD=VALUE (also != >= <= > <, repeatable), --fields PATHS, --format tsv, and runs list --experiment REF. Selection and projection only; no values are computed.',
|
|
56
|
+
requiresApi: false,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
summary: 'runs list and experiments list exclude superseded records (extras.superseded_by) and work under abandoned experiments by default; --include-superseded and --include-abandoned restore them. This changes the default output of an existing command.',
|
|
60
|
+
requiresApi: false,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
summary: 'fieldwork changelog reports recent releases offline, marking which changes need an updated API.',
|
|
64
|
+
requiresApi: false,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
summary: 'Network failures, timeouts and invalid server responses have distinct error codes and retry guidance; API failures retain the server request ID.',
|
|
68
|
+
requiresApi: false,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
summary: 'runs record REF applies observations, extras, comparison context, artifacts, a status, an error summary or a logs URI in one call; the server reads the current revision, a result may be recorded directly from planned, and an identical revisionless retry reads back the committed result.',
|
|
72
|
+
requiresApi: true,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
summary: 'runs create accepts status, stub, startedAt, finishedAt and errorSummary, so a finished or historical run is a single call. Dates are order-checked and never invented.',
|
|
76
|
+
requiresApi: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
summary: 'Schema fields support type ref with optional refKind recipe or file, validated against the product artifact registry. A recorded ref links and freezes its artifact, and works as a bar X axis and groupBy key.',
|
|
80
|
+
requiresApi: true,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
summary: 'Charts admit runs pinned to other versions of the same schema when every charted field keeps its value kind, unit and direction; schemaVersions "pinned" opts out, and charts data reports each version with its verdict.',
|
|
84
|
+
requiresApi: true,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
summary: 'charts data leaves out runs marked extras.superseded_by, reporting each under excluded with the replacement named, so a pooled run can no longer be averaged in beside the per-set runs that replaced it and understate the spread.',
|
|
88
|
+
requiresApi: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
summary: 'Experiment and run responses repeat schemaVersionId, varying, observations, comparisonContext, extras and artifactIds at the top level; research remains canonical.',
|
|
92
|
+
requiresApi: true,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
function order(version) {
|
|
98
|
+
return version.split('.').map(Number);
|
|
99
|
+
}
|
|
100
|
+
export function compareVersions(a, b) {
|
|
101
|
+
const [left, right] = [order(a), order(b)];
|
|
102
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
103
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
104
|
+
if (difference)
|
|
105
|
+
return difference;
|
|
106
|
+
}
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
function invalid(message, hint) {
|
|
110
|
+
throw Object.assign(new Error(message), { code: 'UNKNOWN_VERSION', hint });
|
|
111
|
+
}
|
|
112
|
+
export function selectChangelog(cliVersion, options = {}) {
|
|
113
|
+
const oldest = entries[entries.length - 1].version;
|
|
114
|
+
for (const value of [options.since, options.release]) {
|
|
115
|
+
if (value !== undefined && !/^\d+(\.\d+)*$/.test(value))
|
|
116
|
+
invalid(`Not a version number: ${value}`, 'Use a dotted version such as 0.4.0.');
|
|
117
|
+
}
|
|
118
|
+
let selected = [...entries];
|
|
119
|
+
if (options.release) {
|
|
120
|
+
selected = selected.filter((entry) => entry.version === options.release);
|
|
121
|
+
if (!selected.length)
|
|
122
|
+
invalid(`Release ${options.release} is not bundled with this CLI`, `This package carries recent releases only, back to ${oldest}. See ${fullHistory}.`);
|
|
123
|
+
}
|
|
124
|
+
if (options.since)
|
|
125
|
+
selected = selected.filter((entry) => compareVersions(entry.version, options.since) > 0);
|
|
126
|
+
// An older --since must not read as "nothing else ever changed".
|
|
127
|
+
const truncated = !options.release && !!options.since && compareVersions(options.since, oldest) < 0;
|
|
128
|
+
return {
|
|
129
|
+
cliVersion,
|
|
130
|
+
recentOnly: true,
|
|
131
|
+
oldestBundled: oldest,
|
|
132
|
+
fullHistory,
|
|
133
|
+
...(truncated ? { truncated: true } : {}),
|
|
134
|
+
note: `Recent releases only, back to ${oldest}${truncated ? `; changes before ${oldest} are not bundled` : ''}. requiresApi states what a change needs from the server, which this CLI cannot verify: a newer client does not upgrade an API.`,
|
|
135
|
+
entries: selected,
|
|
136
|
+
};
|
|
137
|
+
}
|
package/dist/client.js
CHANGED
|
@@ -1,12 +1,85 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const retryHint = (method) => method === 'GET'
|
|
2
|
+
? 'This read is safe to retry.'
|
|
3
|
+
: 'The server may have committed this write. Inspect the target record before retrying.';
|
|
4
|
+
export async function request(baseUrl, path, method = 'GET', body, credentials = {}) {
|
|
5
|
+
const origin = baseUrl.replace(/\/$/, '');
|
|
6
|
+
let response;
|
|
7
|
+
try {
|
|
8
|
+
response = await fetch(`${origin}/api/v1${path}`, {
|
|
9
|
+
method,
|
|
10
|
+
headers: {
|
|
11
|
+
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
12
|
+
...(credentials.token ? { Authorization: `Bearer ${credentials.token}` } : {}),
|
|
13
|
+
...(credentials.organization
|
|
14
|
+
? { 'X-Fieldwork-Organization': credentials.organization }
|
|
15
|
+
: {}),
|
|
16
|
+
},
|
|
17
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
18
|
+
signal: AbortSignal.timeout(15000),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
const timedOut = error instanceof Error && ['AbortError', 'TimeoutError'].includes(error.name);
|
|
23
|
+
// Named, because the server is resolved from a flag, two environment variables, a
|
|
24
|
+
// workspace file or a default, and "could not reach the server" leaves the reader to
|
|
25
|
+
// guess which of those was in force. The origin carries no credential: tokens travel
|
|
26
|
+
// in a header.
|
|
27
|
+
throw Object.assign(new Error(timedOut
|
|
28
|
+
? `Request to ${origin} timed out after 15 seconds`
|
|
29
|
+
: `Could not reach the server at ${origin}`), {
|
|
30
|
+
code: timedOut ? 'REQUEST_TIMEOUT' : 'NETWORK_ERROR',
|
|
31
|
+
hint: retryHint(method),
|
|
32
|
+
cause: error,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
6
35
|
if (response.status === 204)
|
|
7
36
|
return { deleted: true };
|
|
8
|
-
|
|
37
|
+
let data;
|
|
38
|
+
try {
|
|
39
|
+
data = (await response.json());
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw Object.assign(new Error(`Server returned an invalid response (HTTP ${response.status})`), {
|
|
43
|
+
code: 'INVALID_RESPONSE',
|
|
44
|
+
status: response.status,
|
|
45
|
+
hint: retryHint(method),
|
|
46
|
+
cause: error,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
9
49
|
if (!response.ok)
|
|
10
|
-
throw Object.assign(new Error(data.message ?? `HTTP ${response.status}`), {
|
|
50
|
+
throw Object.assign(new Error(data.message ?? `HTTP ${response.status}`), {
|
|
51
|
+
code: data.code ?? 'HTTP_ERROR',
|
|
52
|
+
status: response.status,
|
|
53
|
+
...(response.status === 401 || response.status === 403
|
|
54
|
+
? {
|
|
55
|
+
hint: response.status === 401
|
|
56
|
+
? 'Supply a credential with --token, FIELDWORK_TOKEN, or workspace configuration. Retrying without one will not succeed.'
|
|
57
|
+
: 'This credential is valid but not permitted here. Check the selected organization with --org or FIELDWORK_ORGANIZATION, and that its access has not been revoked.',
|
|
58
|
+
}
|
|
59
|
+
: {}),
|
|
60
|
+
...(data.details ? { details: data.details } : {}),
|
|
61
|
+
...(data.requestId ? { requestId: data.requestId } : {}),
|
|
62
|
+
});
|
|
11
63
|
return data;
|
|
12
64
|
}
|
|
65
|
+
/** Every page of a paged collection.
|
|
66
|
+
*
|
|
67
|
+
* The CLI filters, groups and formats over whole collections -- `--where`, superseded and
|
|
68
|
+
* abandoned exclusions, field selection -- so a page of them would apply each filter to a
|
|
69
|
+
* page and call it the answer. Walking is the honest reading of the same request; when a
|
|
70
|
+
* campaign is large enough for that to hurt, the filters belong in the query.
|
|
71
|
+
*/
|
|
72
|
+
export async function requestAll(baseUrl, path, credentials = {}) {
|
|
73
|
+
const all = [];
|
|
74
|
+
let cursor = null;
|
|
75
|
+
do {
|
|
76
|
+
const separator = path.includes('?') ? '&' : '?';
|
|
77
|
+
const page = (await request(baseUrl, cursor ? `${path}${separator}cursor=${encodeURIComponent(cursor)}` : path, 'GET', undefined, credentials));
|
|
78
|
+
// A collection that is not paged answers with an array, and is its own only page.
|
|
79
|
+
if (Array.isArray(page))
|
|
80
|
+
return page;
|
|
81
|
+
all.push(...(page.items ?? []));
|
|
82
|
+
cursor = page.nextCursor ?? null;
|
|
83
|
+
} while (cursor);
|
|
84
|
+
return all;
|
|
85
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
export function credentialsPath() {
|
|
5
|
+
const base = process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config');
|
|
6
|
+
return join(base, 'fieldwork', 'credentials.json');
|
|
7
|
+
}
|
|
8
|
+
/** Origins are compared without a trailing slash so http://host and http://host/ agree. */
|
|
9
|
+
const key = (url) => url.replace(/\/$/, '');
|
|
10
|
+
function read() {
|
|
11
|
+
const path = credentialsPath();
|
|
12
|
+
if (!existsSync(path))
|
|
13
|
+
return { version: 1, servers: {} };
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
16
|
+
return parsed?.servers && typeof parsed.servers === 'object'
|
|
17
|
+
? { version: 1, servers: parsed.servers }
|
|
18
|
+
: { version: 1, servers: {} };
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// A corrupted file must not lock the CLI out: an explicit --token or FIELDWORK_TOKEN
|
|
22
|
+
// still works, and `auth login` overwrites it.
|
|
23
|
+
return { version: 1, servers: {} };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function storedCredential(url) {
|
|
27
|
+
return read().servers[key(url)];
|
|
28
|
+
}
|
|
29
|
+
export function storeCredential(url, credential) {
|
|
30
|
+
const path = credentialsPath();
|
|
31
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
32
|
+
const file = read();
|
|
33
|
+
file.servers[key(url)] = credential;
|
|
34
|
+
// Written before chmod, so the window where it exists with default permissions is as
|
|
35
|
+
// small as possible; the directory is already owner-only.
|
|
36
|
+
writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
|
|
37
|
+
chmodSync(path, 0o600);
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
export function forgetCredential(url) {
|
|
41
|
+
const file = read();
|
|
42
|
+
if (!file.servers[key(url)])
|
|
43
|
+
return false;
|
|
44
|
+
delete file.servers[key(url)];
|
|
45
|
+
const path = credentialsPath();
|
|
46
|
+
if (Object.keys(file.servers).length === 0)
|
|
47
|
+
rmSync(path, { force: true });
|
|
48
|
+
else {
|
|
49
|
+
writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
|
|
50
|
+
chmodSync(path, 0o600);
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
/** Resolution order, matching how --url resolves: explicit, then environment, then stored.
|
|
55
|
+
* The source is reported so `auth status` can say which one is in play -- the usual
|
|
56
|
+
* confusion is a stale environment variable shadowing a fresh login. */
|
|
57
|
+
export function resolveCredential(url, flags) {
|
|
58
|
+
if (flags.token)
|
|
59
|
+
return { token: flags.token, organization: flags.org, source: 'flag' };
|
|
60
|
+
const fromEnvironment = process.env['FIELDWORK_TOKEN'];
|
|
61
|
+
if (fromEnvironment)
|
|
62
|
+
return {
|
|
63
|
+
token: fromEnvironment,
|
|
64
|
+
organization: flags.org ?? process.env['FIELDWORK_ORGANIZATION'],
|
|
65
|
+
source: 'environment',
|
|
66
|
+
};
|
|
67
|
+
const stored = storedCredential(url);
|
|
68
|
+
if (stored)
|
|
69
|
+
return {
|
|
70
|
+
token: stored.token,
|
|
71
|
+
organization: flags.org ?? process.env['FIELDWORK_ORGANIZATION'] ?? stored.organization,
|
|
72
|
+
source: 'file',
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
organization: flags.org ?? process.env['FIELDWORK_ORGANIZATION'],
|
|
76
|
+
source: 'none',
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/fieldwork-skill.md
CHANGED
|
@@ -9,16 +9,17 @@ Use this skill when a user asks you to organize or report long-running research
|
|
|
9
9
|
|
|
10
10
|
## Prerequisites and invocation
|
|
11
11
|
|
|
12
|
-
You need Node.js 22+, the `@agentionai/fieldwork-cli` package installed on PATH, and access to a running compatible Fieldwork API server. The CLI package contains no server or web app and does not start either. Publication is pending; install a prepared local archive with `npm install --global ./agentionai-fieldwork-cli-0.
|
|
12
|
+
You need Node.js 22+, the `@agentionai/fieldwork-cli` package installed on PATH, and access to a running compatible Fieldwork API server. The CLI package contains no server or web app and does not start either. Publication is pending; install a prepared local archive with `npm install --global ./agentionai-fieldwork-cli-0.7.0.tgz`, or the repository installer. After publication, use `npm install --global @agentionai/fieldwork-cli@0.7.0`. Installing from npm does not require pnpm or a checkout.
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
15
|
fieldwork --help
|
|
16
|
-
fieldwork
|
|
16
|
+
fieldwork auth status
|
|
17
|
+
fieldwork products list
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
For source development only: install with `pnpm install`, build with `pnpm --filter @agentionai/fieldwork-cli build`, and substitute `node apps/cli/dist/main.js` for `fieldwork` from the checkout root. Outside the checkout use its actual path, never an invented path. Package-manager wrappers may add output.
|
|
20
21
|
|
|
21
|
-
Server precedence is `--url`, `FIELDWORK_URL`, legacy `LAB_URL`, nearest workspace config, then `http://127.0.0.1:4310`. Supply an HTTP(S) origin, NOT a URL ending in `/api/v1` or `/products/ID`; credentials, query strings and fragments are not accepted. Workspace/tenant is configured on the server; product scoping is not authentication. Confirm the intended server and product before writing. Do not start a second server or change its database to work around connection errors.
|
|
22
|
+
Server precedence is `--url`, `FIELDWORK_URL`, legacy `LAB_URL`, nearest workspace config, then the hosted service `https://app.fieldworkledger.com`. A local server must be named explicitly, for example `--url http://127.0.0.1:4310`. The hosted service requires a credential: an agent credential issued from the web app's Account page, stored once with `fieldwork auth login --token TOKEN` (per server; `auth status` shows which credential is in use). Supply an HTTP(S) origin, NOT a URL ending in `/api/v1` or `/products/ID`; credentials, query strings and fragments are not accepted. Workspace/tenant is configured on the server; product scoping is not authentication. Confirm the intended server and product before writing. Do not start a second server or change its database to work around connection errors.
|
|
22
23
|
|
|
23
24
|
## Hierarchy and scope
|
|
24
25
|
|
|
@@ -61,7 +62,7 @@ Use setup to generate config; do not infer bindings from directory names. Run cr
|
|
|
61
62
|
|
|
62
63
|
## Typed experiment schemas
|
|
63
64
|
|
|
64
|
-
Comparable experiments need declared shapes. A schema version defines `parameters`, `observations`, and `comparisonContext` fields with type, unit, direction, bounds, allowed values, and `compare` flags. Publish on a product for reuse, a campaign for study-specific fields (including product-linked campaigns), or an existing experiment for specialized fields. A scope may use its own schemas and its ancestors’ schemas, never a sibling’s. Schemas are complete definitions, not silently merged overlays.
|
|
65
|
+
Comparable experiments need declared shapes. A schema version defines `parameters`, `observations`, and `comparisonContext` fields with type (`number`, `integer`, `string`, `boolean`, `enum`, `ref`), unit, direction, bounds, allowed values, `refKind`, and `compare` flags. Publish on a product for reuse, a campaign for study-specific fields (including product-linked campaigns), or an existing experiment for specialized fields. A scope may use its own schemas and its ancestors’ schemas, never a sibling’s. Schemas are complete definitions, not silently merged overlays.
|
|
65
66
|
|
|
66
67
|
```sh
|
|
67
68
|
fieldwork schemas publish --product model-a --json '{"stub":"quant-study","version":1,"definition":{"parameters":{"bits":{"type":"enum","values":[4,8],"required":true},"group_size":{"type":"integer","minimum":1}},"observations":{"memory_gib":{"type":"number","unit":"GiB","direction":"minimize","required":true}},"comparisonContext":{"hardware":{"type":"string","compare":true}}}}'
|
|
@@ -74,23 +75,23 @@ Workflow: publish or reuse a version, pin it on the experiment (`schemaVersionId
|
|
|
74
75
|
|
|
75
76
|
Validation is strict about meaning, flexible about completeness: unknown fields are rejected (move exploratory data to `extras`), required fixed parameters block experiment readiness; required varying parameters may be deferred to runs, where all required execution fields must resolve before start, missing observations are allowed on incomplete runs, and failed runs may lack measurements. No string coercion or unit conversion happens. Research-value validation rejections include per-path `code`, `expected`, `receivedType`, `message`, and an actionable `hint`; the CLI prints the server's issue list on stderr. Check payloads with `schemas validate` before writing records.
|
|
76
77
|
|
|
77
|
-
|
|
78
78
|
## CLI reference
|
|
79
79
|
|
|
80
80
|
REF means a stub or UUID. Experiment/run stubs require `--campaign` or a local workspace. JSON fields ending in `Id` still require UUIDs; use `--experiment` for a readable reference. Campaign list/create do not inherit product scope: specify `--product` when needed. Other commands inherit the server from local config.
|
|
81
81
|
|
|
82
82
|
Commands below use `fieldwork` as the executable. `--help` is available on every command.
|
|
83
83
|
|
|
84
|
-
| Group
|
|
85
|
-
|
|
|
86
|
-
| products
|
|
87
|
-
| campaigns
|
|
88
|
-
| experiments | list; get REF; context REF; create --json JSON; update REF --json JSON; delete REF --revision N; all accept [--campaign REF]
|
|
89
|
-
| runs
|
|
90
|
-
| setup
|
|
91
|
-
| context
|
|
92
|
-
|
|
|
93
|
-
|
|
|
84
|
+
| Group | Commands and required options |
|
|
85
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| products | list; get REF; create --json JSON; update REF --json JSON; delete REF |
|
|
87
|
+
| campaigns | list [--product REF]; get REF; context REF; create [--product REF] --json JSON; update REF --json JSON; delete REF --revision N |
|
|
88
|
+
| experiments | list [--where EXPR] [--fields PATHS] [--format json\|tsv] [--include-superseded] [--include-abandoned]; get REF; context REF; create --json JSON; update REF --json JSON; delete REF --revision N; all accept [--campaign REF] |
|
|
89
|
+
| runs | list [--experiment REF] [--where EXPR] [--fields PATHS] [--format json\|tsv] [--include-superseded] [--include-abandoned]; get REF; context REF; create [--experiment REF] --json JSON; record REF --json JSON; update REF --json JSON; delete REF --revision N; all accept [--campaign REF] |
|
|
90
|
+
| setup | campaign --campaign REF [--product REF] [--create --goal TEXT] |
|
|
91
|
+
| context | [--campaign REF] |
|
|
92
|
+
| changelog | [--since VERSION] [--release VERSION]; offline, never contacts the API |
|
|
93
|
+
| schemas | list [--inherited]; publish --json JSON; default; set-default --json JSON; these accept product/campaign/experiment scope; get VERSION_ID; template VERSION_ID; validate VERSION_ID [--ready] --json JSON |
|
|
94
|
+
| charts | fields; list; create --json JSON; these accept [--campaign REF] [--experiment REF]; get CHART_ID; data CHART_ID; series CHART_ID --json JSON; frontier CHART_ID --json JSON; delete CHART_ID |
|
|
94
95
|
|
|
95
96
|
`--json -` reads a JSON object from stdin. Use it for multiline text and configuration files rather than constructing shell strings from untrusted text. Successful data commands emit JSON on stdout. Failures emit JSON on stderr and exit nonzero. Help/version are human-readable. Direct invocation avoids package-manager output mixed into machine-readable streams.
|
|
96
97
|
|
|
@@ -140,8 +141,9 @@ fieldwork experiments update EXPERIMENT_ID --json '{"revision":1,"status":"compl
|
|
|
140
141
|
|
|
141
142
|
- `products get`: product object.
|
|
142
143
|
- `campaigns get` / `context`: campaign fields at the top level, plus `context.product`, `context.generatedAt`, `experiments`, and `runs`.
|
|
143
|
-
- `experiments get` / `context`: `{ entity, context, runs }` where runs
|
|
144
|
-
- `runs get` / `context`: `{ entity, context, runs: [] }`.
|
|
144
|
+
- `experiments get` / `context`: `{ entity, context, runs }` where `runs` is the first page of the experiment's children, `{ items, nextCursor }`. Use `runs list --experiment REF` for all of them.
|
|
145
|
+
- `runs get` / `context`: `{ entity, context, runs: { items: [], nextCursor: null } }`.
|
|
146
|
+
- `campaigns get` / `context`: `experiments` and `runs` are likewise first pages, `{ items, nextCursor }`.
|
|
145
147
|
- Experiment/run live `context` contains `product`, `campaign`, `experiment`, and `generatedAt`; parents include revisions. An independent campaign has `product: null`.
|
|
146
148
|
- A run's `entity.contextSnapshot` captures context at creation. Legacy runs can have `null`; never reconstruct a historical snapshot and present it as known truth.
|
|
147
149
|
- Run config initially shallow-merges experiment parameters with explicit config overrides. Nested objects are replaced at the top level, not deep-merged. Later parent edits do not update run config or snapshots. A config update replaces the entire config object; send the complete intended value.
|
|
@@ -183,7 +185,7 @@ fieldwork schemas set-default --campaign memory-study --json '{"schemaVersionId"
|
|
|
183
185
|
|
|
184
186
|
Replace IDs and revisions with returned values. Product/campaign defaults have their own revision counter (initially 0), separate from the entity revision. `schemas default` reports the local selection, revision, effective schema ID, and source scope. Setting `schemaVersionId: null` clears the local product/campaign selection and restores ancestor fallback; it does not disable typing when an ancestor has a default. Stale writes return 409. Published versions and source data are never edited by this operation.
|
|
185
187
|
|
|
186
|
-
A new experiment pins its explicit `schemaVersionId`, otherwise the campaign default, otherwise the product default. New runs inherit their experiment’s pinned schema; direct runs (or runs of legacy unpinned experiments) resolve the nearest default if no explicit version is supplied. Existing experiments, runs and charts are not repinned when defaults change. An experiment’s pinned version is its default for runs: use `schemas set-default --experiment REF --json '{"schemaVersionId":"RETURNED_SCHEMA_UUID","revision":CURRENT_EXPERIMENT_REVISION}'` (equivalent to `experiments update`)
|
|
188
|
+
A new experiment pins its explicit `schemaVersionId`, otherwise the campaign default, otherwise the product default. New runs inherit their experiment’s pinned schema; direct runs (or runs of legacy unpinned experiments) resolve the nearest default if no explicit version is supplied. Existing experiments, runs and charts are not repinned when defaults change. A legacy experiment with existing runs can be pinned in place when every existing run validates against the proposed schema; the experiment and compatible run pins advance atomically. Breaking proposals are rejected with validation issues. An experiment’s pinned version is its default for runs: use `schemas set-default --experiment REF --json '{"schemaVersionId":"RETURNED_SCHEMA_UUID","revision":CURRENT_EXPERIMENT_REVISION}'` (equivalent to `experiments update`). Experiment pins cannot be cleared with null. To define an experiment-local schema: create the experiment, publish on it, then explicitly pin that version.
|
|
187
189
|
|
|
188
190
|
API: `GET/POST /api/v1/{products|campaigns|experiments}/:id/schemas`; `GET .../schemas?inherited=true`; `GET .../schema-default`; `PUT /api/v1/{products|campaigns}/:id/schema-default` with `{schemaVersionId,revision}`. Experiment pin updates use `PATCH /api/v1/experiments/:id`. Campaign chart field discovery includes schemas owned by descendant experiments, but campaign charts still select exactly one immutable version. Experiment charts cannot use a sibling experiment’s schema. No automatic cross-version merging or unit conversion is performed.
|
|
189
191
|
|
|
@@ -206,6 +208,10 @@ Every point is one successful run with the exact pinned schema version. Missing
|
|
|
206
208
|
|
|
207
209
|
Chart data is live, not an immutable evidence snapshot. `charts data` returns points with source run IDs/revisions, experiment IDs, context-series labels, axis labels/units, exclusions, and `generatedAt`. Use these for provenance; no chart implies statistical significance or a proven hypothesis. The UI refresh button reloads definitions and observations. More than 500 eligible points are available in the data table/API but not drawn.
|
|
208
210
|
|
|
211
|
+
A scatter chart without aggregation can carry a frontier: the runs to join with a dashed line, drawn in X order. It is chosen, never computed -- which runs are comparable, and which trade-off is worth drawing, is a judgement. Set it with `fieldwork charts frontier CHART_ID --json '{"runIds":["RUN_ID",...]}'`; the list replaces any earlier choice, `[]` clears it, and only runs the chart currently plots are accepted (`INVALID_FRONTIER` otherwise). Do not put runs from different comparison contexts on one frontier unless the campaign says they are comparable.
|
|
212
|
+
|
|
213
|
+
By default a raw chart has one series (colour) per comparison context. `fieldwork charts series CHART_ID --json '{"series":{"by":"field","section":"parameters","field":"recipe_family"}}'` colours it by a typed parameter or context field instead; `{"series":{"by":"groups","groups":[{"label":"Ours","runIds":[...]}],"otherLabel":"Shipped"}}` defines the series as groups of runs, for a distinction no single field records. `{"series":null}` restores the default; `charts create` accepts the same `series`. Each point keeps its comparison context in `context`, so colour never hides a comparability difference. Aggregated charts cannot take a series; `INVALID_SERIES` names what was wrong.
|
|
214
|
+
|
|
209
215
|
API equivalents: `GET /api/v1/{campaigns|experiments}/:id/charts/fields`, `GET/POST /api/v1/{campaigns|experiments}/:id/charts`, `GET/DELETE /api/v1/charts/:id`, `GET /api/v1/charts/:id/data`. CLI and API are available; the MCP adapter remains a scaffold.
|
|
210
216
|
|
|
211
217
|
## Fieldwork identity and compatibility
|
|
@@ -218,7 +224,7 @@ New setup uses `.fieldwork/workspace.json` and `fieldwork-skill.md`. Existing `.
|
|
|
218
224
|
|
|
219
225
|
Always capture exit status, stdout, and stderr. A nonzero exit with empty stdout means failure, not `None` or a successful empty record; preserve the structured stderr error. Parse stdout as a success value only after checking exit status. `schemas validate` deliberately also returns the invalid report on stdout when it exits 1. Do not retry a create blindly after an ambiguous transport failure: inspect the intended server/campaign first.
|
|
220
226
|
|
|
221
|
-
|
|
227
|
+
The CLI's experiment/run lists are arrays of record objects regardless of workspace or explicit campaign scope; the CLI walks the API's pages (`{ items, nextCursor }`) for you. Their get/context results wrap the record in `entity`; create/update return the record directly. Do not guess shapes or treat empty stdout as `[]`. If an external wrapper returns strings instead, retain the raw command, version, status, and both streams to diagnose the difference.
|
|
222
228
|
|
|
223
229
|
Experiment `method`, `hypothesis`, `objective`, and `conclusion` have a 4000-character limit; name has a 120-character limit. For longer methods, reference a versioned file and include the exact extraction command and source field in `method`. `comparisonContext`, parameters/config, observations, and extras must be objects where supported, not JSON strings.
|
|
224
230
|
|
|
@@ -240,6 +246,8 @@ Extension accepts new optional observations/parameters, enum expansion, relaxed
|
|
|
240
246
|
|
|
241
247
|
This is the exception to ordinary pin immutability: a new immutable successor is created, with `extendedFrom` and `extensionImpact`, and all matching experiment/run/default/chart pins advance atomically. Old schema definitions, recorded values, execution dates, and creation snapshots remain intact. Revisions increment: reload affected records before writing. Existing terminal runs may then receive optional observations via normal revision-checked updates. No run recreation or fabricated lifecycle is necessary. Dry run writes nothing; apply revalidates and is not reserved by the preview. Stale-source errors require inspection, not a blind retry. Independently published versions are not auto-merged, and breaking re-pinning is still blocked.
|
|
242
248
|
|
|
249
|
+
Widening an enum (adding values) is an ordinary compatible extension. A parameter or comparison-context field added to the schema after runs were recorded can be filled in on those runs, finished ones included, in any later extension: `"backfill":{"packager":{"RUN_ID":"unsloth"}}` (or `"parameters.packager"` / `"comparisonContext.driver"` when a bare name is ambiguous). Backfill accepts only runs created before the field first appeared in the schema's lineage and re-pinned by this extension, and never replaces a recorded value; a run recorded after the field existed keeps its empty value as recorded; values are validated against the new definition, applied in the same atomic write, and listed under `impact.backfilled` (dry runs included). Each run notes them in `research.backfilled` with the supplying schema version and time. A backfilled value describes a run rather than records how it ran, so it stays correctable with a revision-checked `runs update`; executed configuration and context stay frozen. Do not re-record finished runs just to add a field.
|
|
250
|
+
|
|
243
251
|
Recipe/artifact commands are available in CLI 0.4.0 with the updated API. Use the explicit artifacts reference array; a config stub alone is not an enforced recipe link.
|
|
244
252
|
|
|
245
253
|
## Recipes and artifacts (updated server/client required)
|
|
@@ -255,3 +263,53 @@ Do not over-claim: a stored URI/hash records provenance but does not attest that
|
|
|
255
263
|
Chart creation now accepts `aggregation: {groupBy:[{section:"parameters",field:"variant"}],metric:"mean",spread:"band"}`. Supply ordinary typed X/Y axes as before. `metric` supports mean or sample stdev; `spread` supports none, mean±SD whiskers, or mean±SD shaded bands for line charts. Saved grouping/metric comes from chart creation; the web spread selector only changes that view. Discover fields first. No free-form math expressions run.
|
|
256
264
|
|
|
257
265
|
Aggregation buckets preserve X plus existing context boundaries (experiment/context/input/environment) and add the requested grouping fields. For a category mean choose the category as bar X; for a line choose a numeric X and a variant grouping field. Replicate runs get equal weight. Missing group fields are excluded explicitly. Source points remain in the API and table; `aggregates` includes count, mean, sample SD, bounds and contributing IDs/revisions. SD uses n−1, is null for n<2, and is never imputed as zero. Bands are ±1 SD, not confidence intervals or proof of a meaningful effect; singleton bands are absent. Include relevant non-varying parameters in grouping, inspect provenance, and do not confuse identical metadata with experimental equivalence. CLI 0.4.0 includes this reference; aggregation requires the separately updated API and web app.
|
|
266
|
+
|
|
267
|
+
## Selecting and projecting records (updated client required)
|
|
268
|
+
|
|
269
|
+
`experiments list` and `runs list` select and project rows; they never compute new values. Compute in Python, jq or your own code from the JSON.
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
fieldwork runs list --experiment output-verbosity --where 'model=qwopus' --fields parameters.n,observations.accuracy --format tsv
|
|
273
|
+
fieldwork runs list --where 'parameters.n>=8' --where 'eval_set=gsm8k'
|
|
274
|
+
fieldwork experiments list --fields stub,status --format tsv
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`--where` takes `FIELD=VALUE` or `!=`, `>=`, `<=`, `>`, `<`; repeat it for AND. Paths are `section.field` (`parameters`, `observations`, `comparisonContext`, `extras`, `inputRefs`, `environment`) or a record field such as `status`; a bare name is searched across the value sections and an ambiguous one is rejected rather than guessed. A run's parameters are its `config`. Comparisons never coerce across types: `n=4` matches the number 4 and the string "4", but not "four". An empty operand means "no recorded value", so `--where 'extras.superseded_by='` keeps records without one. `>`/`<` compare numbers numerically and everything else as text, so ISO dates order correctly. A path that no returned record has is an error, not a blank column.
|
|
278
|
+
|
|
279
|
+
`--fields` prints only those paths, `--format tsv` prints a header row and tab-separated values with tabs and newlines escaped. JSON output stays an array of objects, projected or whole.
|
|
280
|
+
|
|
281
|
+
**Records superseded by a replacement (`extras.superseded_by`) and work under abandoned experiments are left out by default.** Add `--include-superseded` and `--include-abandoned` to see them. This is a correctness default: those records are not live evidence and averaging them in produces a wrong answer. Say which default applied when you report counts.
|
|
282
|
+
|
|
283
|
+
## Recording an outcome in one call (updated server/client required)
|
|
284
|
+
|
|
285
|
+
`runs create` accepts `status`, `stub`, `startedAt`, `finishedAt` and `errorSummary` alongside observations, so a finished or historical run is one call rather than a create plus two patches. Supplied dates are validated for order and are never invented for you.
|
|
286
|
+
|
|
287
|
+
`fieldwork runs record REF --json '{"status":"succeeded","observations":{"accuracy":0.9}}'` records an outcome on an existing run. The server reads the current revision itself, so there is no read-modify-write race; pass `revision` when you do want the optimistic check. A result may be recorded directly from `planned` — recording an outcome is not a lifecycle transition — but a run that already has a terminal status keeps it: create a new run for another attempt. `record` sets `finishedAt` only for a run this server saw `running`; it never fabricates a date for a historical outcome. Comparison context stays frozen after a run leaves planned.
|
|
288
|
+
|
|
289
|
+
Experiment and run responses now repeat `schemaVersionId`, `varying`, `observations`, `comparisonContext`, `extras` and `artifactIds` at the top level, mirroring what create and update accept. `research` remains the canonical location and holds the same values; an unset `schemaVersionId` echoes as `null`.
|
|
290
|
+
|
|
291
|
+
## Typed artifact references (updated server/client required)
|
|
292
|
+
|
|
293
|
+
A field of type `ref` records an artifact stub (or ID) and is validated against the product's artifact registry, with optional `refKind: recipe | file`. Use it wherever the set of values is an open, growing identity — a head recipe, a corpus, a build — instead of an enum that must be republished for every new member.
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
fieldwork artifacts create --product signal --json '{"stub":"head-v6a","name":"Signal v6a head","definition":{"corpus":"wiki-2026","longAnswerWeight":0.5},"files":[{"role":"head","uri":"file://heads/v6a.safetensors","sha256":"..."}]}'
|
|
297
|
+
fieldwork schemas publish --json '{"stub":"style-eval","version":5,"definition":{"parameters":{"recipe":{"type":"ref","refKind":"recipe","required":true}}}}'
|
|
298
|
+
fieldwork runs create --experiment heads --json '{"title":"v6a on gsm8k","status":"succeeded","config":{"recipe":"head-v6a"},"observations":{"accuracy":0.68}}'
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
An unregistered stub, a wrong-case stub and a free-text name are all rejected (`UNKNOWN_REF` or `INVALID_VALUE`), so refs keep the typo-safety an enum gave you without its closed value list. Recording a ref links the artifact into `research.artifactIds` and freezes it, so the measurement carries the exact recipe, corpus, flags and file hashes it was produced with. Ref fields work as a bar X axis and as a `groupBy` key exactly like enums. A new variant is then a new artifact, never a new schema version. Changing an existing field to or from `ref` is a breaking publication, not an extension.
|
|
302
|
+
|
|
303
|
+
## Charts across schema versions (updated server/client required)
|
|
304
|
+
|
|
305
|
+
A chart pins one immutable version for its axes, units, labels and required comparison context. By default it also admits runs pinned to **other versions of that same schema** when every field the chart reads still records the same kind of value in the same unit and direction. Allowed values, bounds, `refKind` and requiredness may differ: those constrain what a run could record, not what a recorded value means. `enum`, `ref` and `string` count as the same kind, as do `integer` and `number`.
|
|
306
|
+
|
|
307
|
+
Appending enum values, adding optional fields, or retiring an enum in favour of a `ref` therefore no longer strands the history that the new runs exist to be compared against. Pass `"schemaVersions":"pinned"` when creating a chart to demand exactly one version.
|
|
308
|
+
|
|
309
|
+
`charts data` returns `schemaVersions`: every candidate version with its `runs` count and either `included: true` or the `reason` it was refused. Report which versions a figure actually mixes; a unit or direction change is refused there and must stay refused. Schemas with a different stub or owner never join, and a run with no pinned schema is still excluded.
|
|
310
|
+
|
|
311
|
+
## Knowing what this CLI can do (updated client required)
|
|
312
|
+
|
|
313
|
+
`fieldwork changelog` lists recent releases of the installed CLI without contacting the API. Each change carries `requiresApi`: `false` means it works against any compatible server, `true` means it needs server behaviour from the corresponding baseline. Use it when a command is missing (`commander.unknownCommand` means the CLI is too old) or when a command exists but the server rejects it (the API is older than the client).
|
|
314
|
+
|
|
315
|
+
`--since VERSION` and `--release VERSION` narrow the output. The package bundles recent releases only; `oldestBundled` and `fullHistory` in the response say so, and `truncated: true` appears when `--since` reaches past the bundled window. Do not read an absent version as "nothing changed". The command reports what a change requires, never what a server actually provides: there is no compatibility handshake, and installing a newer client does not upgrade an API.
|