| 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 |