Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ packages/core/bench/agent-recall-results.json

# MCP publisher credentials (some publisher versions store login in CWD)
.mcpregistry_*

# Local release preparation tools and install smoke checks
.release-tools/
.release-smoke/
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@

# Changelog

## 0.16.0 - Complete source reads, safe retries and fact freshness

Unreleased.

- **Scoped native search.** GitHub and Notion search follow result pages and retain configured repository/database scope. GitHub's 1000-match cap, server incompleteness, and missing/repeated Notion cursors become explicit search warnings.
- **HTTP deadlines and cancellation.** Shared HTTP requests use a 30-second deadline through retries and response-body consumption (`ATS_HTTP_TIMEOUT_MS`). Caller cancellation stops retry waits and prevents another attempt.
- **Per-project corpus failures.** GitHub and Notion keep healthy project records when another project fails, with source warnings that propagate through retrieval. Partial corpora remain uncached.
- **Reviewed fact freshness.** `ats kg stale --days N` lists active facts due for review. `ats kg confirm FACT --source REF` stages new evidence for approval; ratification appends a confirmation without changing original validity or provenance.
- **Safe mutation retries.** Reads retain transient retry behavior. Mutating requests retry only explicit rate-limit rejections; ambiguous network and gateway failures return without replay. Adapter read-only POST requests can opt in to safe retries. Long server reset times return the rejection instead of retrying too early.
- **Fact ratification integrity.** Fact writes verify the approved payload, serialize store checks with the append, recheck conflicts, and deduplicate repeated ratification by proposal id. CLI ratification claims the approved review item before writing.
- **Bound create keys.** Idempotency keys bind the effective request to its source configuration and acquire a durable claim before writing, including reviewed creates. Changed bindings, concurrent attempts and uncertain outcomes fail closed.
- **Bound batch resumes.** Journals bind each item to its payload and source, record an applying claim before execution, and skip only matching completed operations. Changed, interrupted, failed or legacy unbound entries require inspection. Dry runs preserve journal bytes.
- **Source fact dates.** `kg propose --valid-at ISO --learned-at ISO` records when a fact happened and when it was learned separately from ratification. JSONL accepts `validAt`/`learnedAt`; timestamps normalize to UTC and future/planned dates are refused. Cypher and Graphiti exports retain both times.


## 0.15.0 - Reviewed writes and CLI state integrity

Released 2026-10-02.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ ATS is a good fit when operational context already lives in task systems or conn

ATS-managed execution metadata can be encoded in the task body, with typed links under `## Related` and consulted sources under `## References`. Managed helpers are designed to preserve human-authored rows and links; `update --content` replaces the complete body, so callers that add to a body use `--append` / `--prepend`, present the `contentHash` they read with `--if-match`, and verify the result. [`npm run prove:intent`](examples/intent-layer/) runs a deterministic synthetic proof of the execution-context path.

For reliable automation and freshness workflows, see [CLI reliability](docs/cli-reliability.md): scoped pagination, bounded requests, payload-bound retry/resume keys, and reviewed fact confirmations.

## Minimal adapter-neutral workflow

1. **Select and verify an adapter:** `ats config use <adapter>`, authenticate as its README describes, then run `ats doctor`.
Expand Down
43 changes: 43 additions & 0 deletions docs/cli-reliability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# CLI reliability

## Complete reads

GitHub and Notion native search follow result pages and retain the adapter's configured repository or database allow-list, including when the query itself names another repository. GitHub limits search to 1000 matches and can report incomplete results. Notion search stops with a warning on missing/repeated cursors or after 100 pages. Narrow the query when a warning reports a bound.

A failed GitHub repository or Notion database leaves healthy corpus records available. The result carries per-source warnings; Core does not cache partial corpora. `ats find QUERY --require-complete --json` preserves its diagnostic output and returns exit 2 for degraded reads.

## Bounded requests and safe retries

`ATS_HTTP_TIMEOUT_MS` is a positive millisecond deadline, default 30000, spanning request attempts, retry waits, and response-body reads. The shared HTTP helper accepts `signal` and forwards cancellation to fetch. A cancelled wait cannot start another request. Large server reset times return the rate-limit response instead of retrying before the requested reset.

Reads retry transient network/gateway failures. Mutations retry explicit rate-limit rejection only. An ambiguous gateway/network failure might follow a successful remote write, so inspect the backend before repeating it. Adapter authors can mark a read-only POST with `retrySafe: true`; Notion search and database queries use this contract.

## Create keys and batch journals

```sh
ats create writing "Release checklist" --idempotency-key release-checklist --json
ats batch changes.jsonl --journal changes.journal.jsonl --json
```

A create key binds the effective normalized payload and source scope. A matching completed request replays its recorded task or review item. A changed payload or source returns exit 3. The claim is durable before a create, including applying a reviewed create; concurrent callers cannot acquire it again. In-flight/uncertain claims never expire into automatic duplicates.

A journal binds each item id to its full operation and source scope. An applying claim precedes execution. A matching applied/staged operation is skipped on resume; altered, failed, or unfinished operations fail with per-item diagnostics and batch exit 5. Dry-run executes validation/planning without writing or claiming the journal. Journaled claims also exclude concurrent processes.

Claims are intentionally conservative. A crash after a backend write and before local recording can leave an uncertain outcome. Inspect the source and local receipt before staging fresh work. Older keys/journals without payload bindings cannot safely resume: inspect them, then use a fresh key or journal. Source scope includes configuration and working directory; changing either can require a fresh binding. Claims do not provide a distributed transaction with the backend.

## Fact dates and freshness

```sh
ats kg propose "Acme" "uses" "Invoice service" --source "source-record" \
--valid-at 2026-01-01T00:00:00Z --learned-at 2026-02-01T00:00:00Z --json
ats kg stale --days 60 --domain sales --json
ats kg confirm FACT_ID --source "verified source record" --json
ats review approve REVIEW_ID
ats kg ratify REVIEW_ID --json
```

Explicit source times must be ISO timestamps with a timezone, normalize to UTC and cannot be future/planned dates. JSONL proposals accept `validAt` and `learnedAt`. Ratified facts carry `tValid`, `tLearned` and `provenance.ratifiedAt`; omitted valid time retains ratification-time behavior, while omitted learned time uses staging time. `--as-of` continues to query validity intervals; learned time discloses when historical evidence entered the workflow. Exports retain both times.

Freshness age uses the last reviewed confirmation, then learned/ratified time, then legacy validity time. Age is a review signal and never automatically closes a fact. Confirmation requires source evidence, approval and ratification, then appends `lastConfirmedAt` and confirmation provenance while retaining the original fact.

Every ratification checks the approved payload digest and rechecks active facts under the same lock as its append. A conflicting proposal cannot silently become current because another fact was approved first. Repeated ratification of one proposal cannot append twice. The CLI durably claims review items and reports failed ratifications with exit 5. Legacy approvals without digests need a fresh proposal and approval.
6 changes: 5 additions & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ npm ci
npm test
npm run check:publish
npm run check:release
mcp-publisher validate server.json
# Validate server.json against its declared JSON schema without publishing.
```

Some publisher versions advertise `validate` but do not implement it. In that
case use a JSON Schema validator against the schema declared in `server.json`;
do not use `publish` as a validation probe.

Check npm credentials with `npm whoami`. Publish core first, then the public
adapters, then CLI and MCP; consumers must not receive a package whose required
ATS dependency version is missing from npm. Use `npm publish --access public
Expand Down
60 changes: 30 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentic-task-system",
"version": "0.15.0",
"version": "0.16.0",
"private": true,
"type": "module",
"description": "Your task manager is the best agent memory you're not using. Agent-native context layer over your existing task app, with hybrid retrieval (RRF) and pluggable storage adapters.",
Expand Down
4 changes: 2 additions & 2 deletions packages/adapter-airtable/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reneza/ats-adapter-airtable",
"version": "0.15.0",
"version": "0.16.0",
"description": "Airtable adapter for Agentic Task System. Expose any Airtable base as agent-queryable records through ATS retrieval, RRF fusion, and MCP — a table is a project, a record is a task. Adapter, not migration.",
"type": "module",
"main": "index.js",
Expand All @@ -12,7 +12,7 @@
"test": "node --test"
},
"peerDependencies": {
"@reneza/ats-core": "^0.15.0"
"@reneza/ats-core": "^0.16.0"
},
"dependencies": {},
"repository": {
Expand Down
4 changes: 2 additions & 2 deletions packages/adapter-beads/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reneza/ats-adapter-beads",
"version": "0.15.0",
"version": "0.16.0",
"description": "Beads adapter for Agentic Task System using the official bd JSON CLI over repository-local Dolt state.",
"type": "module",
"main": "index.js",
Expand All @@ -13,7 +13,7 @@
"prepublishOnly": "node ../../scripts/check-no-pii.mjs --self"
},
"peerDependencies": {
"@reneza/ats-core": "^0.15.0"
"@reneza/ats-core": "^0.16.0"
},
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions packages/adapter-composite/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reneza/ats-adapter-composite",
"version": "0.15.0",
"version": "0.16.0",
"description": "Cross-source adapter for the Agentic Task System. Query GitHub + Notion + TickTick + any ATS backends as ONE fused corpus: a single `ats find` returns one RRF-ranked list across all of them, each result tagged with its backend. The thing a single-vendor MCP server can't do.",
"type": "module",
"main": "index.js",
Expand All @@ -12,7 +12,7 @@
"test": "node --test"
},
"peerDependencies": {
"@reneza/ats-core": "^0.15.0"
"@reneza/ats-core": "^0.16.0"
},
"dependencies": {},
"repository": {
Expand Down
27 changes: 25 additions & 2 deletions packages/adapter-github/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,31 @@ export async function patchIssue(owner, repo, number, body, cfg = loadConfig())
export async function searchIssues(query, cfg = loadConfig()) {
const scope = cfg.repos.map((r) => `repo:${r}`).join(' ');
const q = `${query} is:issue${scope ? ' ' + scope : ''}`.trim();
const { json } = await gh('/search/issues', { query: { q, per_page: PER_PAGE }, cfg });
return Array.isArray(json.items) ? json.items.filter((it) => !isPullRequest(it)) : [];
const items = [];
const warnings = [];
const seen = new Set();
const allowed = new Set(cfg.repos.map((repo) => repo.toLowerCase()));
for (let page = 1; page <= 10; page++) {
const { json, res } = await gh('/search/issues', { query: { q, per_page: PER_PAGE, page }, cfg });
if (!Array.isArray(json.items)) throw new Error('GitHub search: invalid items response');
if (json.incomplete_results) warnings.push({ source: 'github', error: 'GitHub returned incomplete search results' });
for (const item of json.items) {
const repo = String(item.repository_url || '').match(/repos\/([^/]+\/[^/]+)\/?$/)?.[1];
if (isPullRequest(item) || (allowed.size && (!repo || !allowed.has(repo.toLowerCase())))) continue;
const key = `${repo}:${item.number}`;
if (!seen.has(key)) { seen.add(key); items.push(item); }
}
if (page === 10 && (json.total_count > 1000 || nextLink(res))) {
warnings.push({ source: 'github', error: 'GitHub search is capped at 1000 matches; narrow the query' });
}
if (!nextLink(res) && !(json.total_count > page * PER_PAGE)) break;
if (json.items.length === 0) {
warnings.push({ source: 'github', error: 'GitHub search stopped before the declared match count' });
break;
}
}
items.warnings = warnings;
return items;
}

export function urlForIssue(owner, repo, number) {
Expand Down
11 changes: 9 additions & 2 deletions packages/adapter-github/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,25 @@ const adapter = {
const cfg = loadConfig();
const repos = await listRepos(cfg);
const tasks = [];
adapter.__fetchWarnings = [];
for (const r of repos) {
const { owner, repo } = splitProjectId(r.id);
const issues = await listIssues(owner, repo, cfg);
for (const it of issues) tasks.push(issueToTask(owner, repo, it));
try {
const issues = await listIssues(owner, repo, cfg);
for (const it of issues) tasks.push(issueToTask(owner, repo, it));
} catch (error) {
adapter.__fetchWarnings.push({ source: r.id, error: error.message });
}
}
return tasks;
},

async searchByQuery(query) {
const q = String(query || '').trim();
adapter.__searchWarnings = [];
if (!q) return [];
const items = await searchIssues(q, loadConfig());
adapter.__searchWarnings = items.warnings || [];
return items.map((it) => {
const repoUrl = String(it.repository_url || '');
const m = repoUrl.match(/repos\/([^/]+)\/([^/]+)\/?$/);
Expand Down
Loading
Loading