返回 last30days-skill
json-export.md
根目录 / docs / reference / json-export.md
1 # Agent JSON export
2
3 The agent JSON profile is the stable machine-readable research contract for downstream agents, scripts, dashboards, and workflow tools. Ask the slash command for machine-readable JSON:
4
5 ```text
6 /last30days AI coding agents — return the versioned agent JSON export
7 ```
8
9 For direct engine use in scripts, cron jobs, or development, use:
10
11 ```bash
12 python3 skills/last30days/scripts/last30days.py "AI coding agents" --emit=json
13 python3 skills/last30days/scripts/last30days.py "AI coding agents" --emit=json --output results.json
14 ```
15
16 `--emit=json` defaults to `--json-profile=agent`. The full internal report remains available for debugging and power users:
17
18 ```bash
19 python3 skills/last30days/scripts/last30days.py "AI coding agents" --emit=json --json-profile=raw
20 ```
21
22 The raw profile is intentionally unversioned and may change when pipeline internals change. It preserves the JSON serialization used before the agent profile was introduced.
23
24 ### Local corpus privacy
25
26 Evidence from `--corpus` / `LAST30DAYS_CORPUS_DIRS` is excluded from the versioned agent profile by default. The exclusion removes corpus results, corpus-only clusters, corpus source outcomes, freshness verdicts, and titles derived from a corpus representative. Set `LAST30DAYS_CORPUS_IN_EXPORT=1` only for a run whose JSON is intentionally allowed to contain local file contents. This opt-in does not change the schema shape or version; it permits `source: "corpus"` entries in the existing result fields. The unversioned `raw` profile is a complete local debug dump and may contain corpus paths and text.
27
28 ## Discovery export
29
30 Discovery mode has a separate versioned contract so its topic results do not change the normal research export:
31
32 ```bash
33 python3 skills/last30days/scripts/last30days.py --discover "AI agents" --emit=json
34 ```
35
36 Its top level contains `schema_version` (`1.1`), `kind` (`"discovery"`), `domain` (`""` for a global no-domain trending run), `generated_at`, `window_days`, `source_status`, `feeds`, `results`, `warnings`, `outcome` (`"ok"`, or `"nothing-solid"` when no topic cleared the confidence floor), and `weak_signal` (the closest sub-floor topic name on a nothing-solid run, else `null`). Each ranked result contains `rank`, `topic`, `why_spiking`, `momentum` (`new-this-week` or `building`), `velocity_score`, `sources`, per-source native `engagement`, a ready-to-run `command`, `evidence_urls`, `top_comment` (the strongest verbatim community comment from the topic's research pass, with attribution; `null` on shallow runs), `corroboration_count` (distinct confirming sources), `podcast_angle` (engine-generated podcast content hook; `null` when no reasoning provider produced one), `x_article_angle` (engine-generated X-article content hook; `null` when no reasoning provider produced one), `previously_surfaced_count` (topic-queue annotation: how many earlier sweeps surfaced this topic; `0` when the queue is off), `last_surfaced` (topic-queue annotation: date the topic last surfaced; `null` when the queue is off), and `covered` (topic-queue annotation: whether the topic was already covered; `false` when the queue is off). The discovery contract follows the same versioning policy below but evolves independently of the normal agent export. `--json-profile=raw` returns the unversioned internal `DiscoveryReport` dataclass instead.
37
38 When `LAST30DAYS_API_KEY` and `LAST30DAYS_API_BASE` route a run through a configured remote API, the server does not return the local `Report` needed to build this profile. In that mode, `--json-profile=agent` exits with status 2 instead of emitting a misleading shape; use `--json-profile=raw` to retain the remote backend's existing server-response JSON contract.
39
40 ## Top-level fields
41
42 | Field | Type | Meaning |
43 | --- | --- | --- |
44 | `schema_version` | string | Agent export contract version. The current version is `1.2`. |
45 | `query` | string | The research topic supplied to the engine. |
46 | `generated_at` | string | UTC generation timestamp in RFC 3339 format. |
47 | `window_days` | integer | Number of days between the report's start and end dates. |
48 | `source_status` | object | Map of source name to the outcome observed during this run. |
49 | `freshness_verdicts` | array | Per-claim act-time verdicts produced by `--verify-freshness`; empty when verification was not requested or no conservative claims were extractable. |
50 | `clusters` | array | Ranked groups of related results. |
51 | `results` | array | Ranked, flat evidence results for downstream processing. |
52
53 All top-level fields are always present. Empty runs contain empty `clusters` and `results` arrays. Sources appear in `source_status` when the run recorded an outcome for them.
54
55 ## `freshness_verdicts`
56
57 Each entry identifies the grounded claim and candidate, its primary source item, the typed `verdict` (`current`, `stale`, `contradicted`, or `unsupported`), the original and re-derived values when applicable, and source/evidence URLs and timestamps. `stale` means a successful point re-fetch returned a moved value; `contradicted` means a newer item in the report window explicitly disagrees; `unsupported` means the datum could not be re-checked, including degraded `source_status` outcomes. Consumers can gate actions on `verdict == "current"` without treating an unreachable source as evidence that a claim moved.
58
59 ## `source_status`
60
61 Each value distinguishes a clean empty result from incomplete coverage:
62
63 | State | Meaning |
64 | --- | --- |
65 | `ok` | The source completed and returned one or more items. |
66 | `no-results` | The source completed successfully but found no matching items. |
67 | `partial` | The source returned some items before a later failure. |
68 | `rate-limited` | Retrieval was stopped by a provider rate limit. |
69 | `auth-failed` | Credentials were missing, rejected, or expired during retrieval. |
70 | `unreachable` | The source or network endpoint could not be reached. |
71 | `timeout` | Retrieval exceeded its time limit. |
72 | `schema-drift` | The provider response no longer matched the expected shape. |
73 | `skipped-unconfigured` | The source was intentionally skipped because required configuration was absent. |
74 | `error` | Retrieval failed for another reason. |
75
76 Consumers must not interpret failure states as evidence that a source had no discussion. Only `no-results` means the source completed cleanly with zero matches.
77
78 ## Cluster fields
79
80 | Field | Type | Meaning |
81 | --- | --- | --- |
82 | `title` | string | Cluster headline. |
83 | `summary` | string | Summary from the cluster's representative ranked result. |
84 | `sources` | array of strings | Sources represented by the cluster. |
85 | `engagement_total` | number | Sum of one headline native engagement counter per result. Known sources use their primary count (for example, Digg uses `postCount`); otherwise the largest counter-like field is used. Ranking, ratio, rating, and computed-score metadata are excluded. |
86
87 Cluster array order is ranking order. A result's `cluster` value is the zero-based index into this array.
88
89 ## Result fields
90
91 | Field | Type | Meaning |
92 | --- | --- | --- |
93 | `candidate_id` | string | Stable identifier joining this result to `freshness_verdicts[].candidate_id`. Added in `1.2`. |
94 | `title` | string | Result title. |
95 | `source` | string | Primary source name, such as `reddit`, `x`, `youtube`, or `grounding`. |
96 | `url` | string | Canonical result URL. It may be empty when the provider supplies no link. |
97 | `published_at` | string | Primary source item's publication date or timestamp. Omitted when unknown. |
98 | `summary` | string | Normalized snippet, with the relevance explanation or body used as fallback. |
99 | `engagement` | object | Native engagement counters from the primary source item, such as Reddit `score` and `num_comments` or X `likes` and `reposts`. |
100 | `relevance_score` | number | Engine final score normalized to the inclusive `0.0`–`1.0` range. |
101 | `cluster` | integer | Zero-based index into `clusters`. Omitted when the result is not assigned to a cluster. |
102
103 Fields whose value is unknown are omitted rather than emitted as JSON `null`. Strings and collection fields otherwise remain present, including empty strings, objects, or arrays.
104
105 ## Comparison runs
106
107 Comparison queries use an envelope so each entity keeps its own contract:
108
109 ```json
110 {
111 "schema_version": "1.2",
112 "comparison": true,
113 "entities": ["OpenAI", "Anthropic"],
114 "reports": [
115 {"entity": "OpenAI", "report": {"schema_version": "1.2", "query": "OpenAI"}},
116 {"entity": "Anthropic", "report": {"schema_version": "1.2", "query": "Anthropic"}}
117 ]
118 }
119 ```
120
121 The abbreviated reports above only illustrate the envelope; real reports contain every documented top-level field.
122
123 ## Versioning policy
124
125 - `schema_version` uses `major.minor` numbering.
126 - Any breaking field removal, rename, type change, semantic change, or envelope change requires a major-version bump.
127 - Backward-compatible field additions may use a minor-version bump. Consumers should ignore fields they do not recognize.
128 - The checked-in golden snapshot test locks the complete current shape. Contract changes must update the version and snapshot deliberately.
129 - `1.2` added `candidate_id` to each `results` entry so verdicts can be joined to the result they annotate.
130 - Discovery `1.1` added `podcast_angle`, `x_article_angle`, `previously_surfaced_count`, `last_surfaced`, and `covered` to each discovery `results` entry — a backward-compatible minor bump; the fields carry their defaults (`null`/`null`/`0`/`null`/`false`) until an angle generator or the topic queue populates them.
131 - `--json-profile=raw` is outside this compatibility policy because it mirrors internal pipeline dataclasses.
132
133 `--preflight --emit=json` is a different machine contract for permission and configuration inspection. `--json-profile` does not alter preflight output.
134
134 lines MARKDOWN