Repository navigation
v0.1.1
Added
- Spec (D-82, owner decision): 04 §2.3 now defines the owner seed blobs.
ALKPv1 is the passkey PRF wrap (owner key and credential ID in the header, random nonce, whole header as AAD, size and owner checks);ALKRv1 is the recovery-code wrap (theALKOlayout with its own magic and label, generated codes only, Argon2id 64 MiB / 3 / 1). M1-1k is unblocked: vectors first, then code. - Security utilities for files (M1-15, S1, 04 §4, §7; not wired into any tool yet, M1-7f and M1-9d use them):
@agentlink/coregets the pure checks (checkFetchUrlon the raw URL string:httpsonly, no numeric host notation in any base, no local or internal names, no user info;pickPublicAddress: every resolved address must be globally routable;decidePathfor both POSIX and Windows paths;sanitizeFileName).@agentlink/nodegetsnodeSafeFetch(one lookup per hop, connect to the checked address, at most 2 redirects each re-checked, byte cap, one deadline, no proxy; errors never carry the URL),resolveAllowedFile/openAllowedFile(realpath on both sides; hidden files,~/.ssh,~/.agentlinkand keychain folders always refused, also through links) andcreateInboxFile(sanitised name, exclusive create, never through a link). In Workers (G-14, proposed) the raw URL check applies and the address check relies on Workers egress; that path lands with M2-3b. - Crypto (M1-1j, S2): EIP-712 typed data and hashing for the OCC v2 proof types
Record,ReceiptandRegistrationunder the chain domain{name: "OCC", version: "2", chainId, verifyingContract}, with strict 05 §3.1 JSON-form parsing (refused, never normalised);recordStructHash; the recipient commitmentkeccak256(abi.encode(recipientAgentEvm, salt)); and EIP-3009ReceiveWithAuthorizationtyped data under a token domain the caller reads from chain. Theeip712-proofvector suite is no longer pending. The revoke struct waits for G-20 (M1-1i-b). - Storage (M1-6a, S2):
@agentlink/storagehas theStorageAdapterinterface (07 §5.1), the inline adapter (ciphertext up to 12 288 bytes, base64url in the payload) and the S3-compatible adapter overaws4fetch: upload, a presigned download link clamped to 7 days and to the expiry of temporary credentials, and delete. Objects are keyedagentlink/<storagePrefix>/<random>with no metadata; credentials come from an injected provider; no credential, link or object key is logged. Not yet wired into the tools; integration tests against MinIO and R2 follow in M1-6a-2. - Local approval server (M1-8d, S2,
@agentlink/node; not wired into the CLI yet, M1-11a): an HTTP server on127.0.0.1and a random port with a view-only session token kept in memory (exchanged for an HttpOnly cookie, never written to disk or logged), exactHostandOriginchecks against DNS rebinding, a CSRF token and non-simple requests for decisions, and owner presence (owner code or owner passphrase) for every approve or deny, signed as oneapproval.decidethat core verifies. Each approval ID is decided once through it; a fixed OS notification and the/approve/<id>route are included. The same-user script test runs against the live server. - Live relay subscriptions (M1-9s): while
agentlink mcpruns and holds the publisher lease, new messages and handshakes are stored and processed as they reach a relay instead of on the next 30 s poll. The coreTransportgainssubscribeand the clientsubscribeInbox(same receive path assyncInbox: dedup by gift-wrap ID and envelopeHash, one store transaction, rate limit). The background loop keeps a catch-up every 5 min as a fallback, polls when no subscription can open, and closes the subscription on lease loss and on shutdown. - Spec text sync (M1-9s): the 07 §4.1 interface sketch now shows
subscribewith the implementedsynchandler shape (relays, identities, persistence, onEnvelope) and an unsubscribe that resolves once in-flight handlers settle; no protocol change. - Dogfood tooling (M1-23-b):
tools/dogfood/run.mjs --role a|bruns the pairing, messages, owner approvals and the J8 negotiation across two Macs, one agent per Mac, with the invite link and run ID exchanged by hand;--same-ownerfor one person at both Macs. Single-machine mode is unchanged. Runbook RB-17 section 10 has the commands. - Test vectors (M1-1i, S2): two new suites, pending until their code lands.
eip712-proof(M1-1j) covers EIP-712 hashing ofRecord,ReceiptandRegistrationunder the OCC v2 chain domain, the recipient commitment, and theRecord/ReceiptJSON-form rejections.prf-wrap(M1-1k) covers the passkey PRF salt and wrap key (04 §2.3). STREAM and revoke vectors wait for G-26 and G-20 (new card M1-1i-b). - CLI (M1-9w, S1):
agentlink init --harness claude-desktopsets up the Claude desktop app (macOS and Windows; its ownclaude_desktop_config.json, not Claude Code's file), with the usual diff, confirmation, backup and refusal of malformed files. On Linux it says the app isn't available there. - Docs (DOCS-SETUP, S1): a beginner page, "Set up AgentLink in your AI tool", with the Node check, one install line per tool, the owner step, a first test, pairing two of your own tools and the top troubleshooting fixes. Claude Desktop is marked as merged but not in v0.1.0.
- Owner approvals (M1-11a, S2, 08 §1, 11 §6.2.1, 04 §2.5.2, D-68): the local approval page on
http://localhost:<port>lists your waiting approvals as cards (who, what, cost, expiry, the rule that asked, and what your AI chose), soonest to expire first. Mark each Approve, Deny or Later, then confirm exactly that list with one passkey gesture or one entry of your owner code; terms are listed but always approved on their own card. Before asking, the page checks the list again and tells you what it left out. Text from your AI or a contact is shown as plain text only. The first-run screen explains what approvals need and offers the passkey, or says when this browser has none and the owner code is the way. Each link the agent opens works once (a link in your browser history no longer opens the page), and after 3 wrong owner codes or passkeys in a row the page makes you wait before the next try (5 seconds, doubling up to 5 minutes);agentlink approvein your own terminal is not affected by that wait. "Remember for this contact" and opening the page from the CLI come later. - File encryption (M1-1d, S2, 04 §4, D-80): the crypto package encrypts and decrypts files in 1 MiB chunks as a stream, so a large file never has to be in memory whole, and refuses a file whose header was changed or whose chunks were dropped, reordered, repeated or added. The
streamtest vectors now run in every runtime. - Test vectors (M1-1i-b, S2): two new suites, pending until their code lands.
stream(M1-1d) coversALF1file encryption with the D-80 chunk AADheader ‖ isFinal(04 §4): empty, 1-byte, one-chunk, chunk + 1, multi-chunk, exact-multiple and inline-cap files, and rejection of tampered headers, reordered, dropped, truncated or appended chunks, a flipped final flag and a wrong nonce.eip712-revocation(M1-1j) covers EIP-712 hashing of the D-81Revocationstruct (05 §4), aRegistrationand aRevocationsharing one owner nonce, and theRevocationJSON-form rejections. - Revocation signing data (M1-1j, S2, 05 §4, D-81): the crypto package builds and hashes the EIP-712
Revocationan owner signs to revoke an agent, under the OCC v2 chain domain with the Registry as verifying contract, and refuses every non-canonical form. Theeip712-revocationtest vectors now run in every runtime. - Crypto (M1-1k, S2):
@agentlink/cryptoseals and opens the owner seed asALKPv1 under a passkey PRF output and asALKRv1 under a generated recovery code (24 base32 characters in six groups of four), with header, size and owner checks, one generic error and no partial output on failure. Theprf-wrapvector suite (80 cases, including cross-type cases withALKOandALKS) is no longer pending. - Hands-free two-Mac dogfood:
node tools/dogfood/run.mjs --two-mac --remote <ssh-host>runs both Macs from Mac A over ssh, syncs the steps by itself and ends with one combined table. The invite link goes over ssh stdin only; owner approvals are still typed by you in the same terminal (M1-23-d, S1). - Auto-sync two-Mac dogfood for macOS:
run.mjs --role b --auto-syncon Mac B, then--role a --auto-sync --remote <host>on Mac A. Each agent runs in its own Mac terminal (ssh only moves small files), so the keychain works; approvals are typed locally on each Mac.--two-macnow detects the macOS keychain-over-ssh failure and points to it (M1-23-e, S1). - Encrypted drop (M1-27a, S2, D-48): the drop Worker in
apps/drop, tested locally under Miniflare and not deployed (deployment is M1-27b; the R2 buckets and lifecycle rule are the owner's M1-28). Signed uploads with no accounts, single-use upload targets that take exactly the declared size (multipart above 64 MiB), a commit that hashes the stored bytes and deletes them on a mismatch, signed downloads until expiry with a download limit of 16, DELETE by the uploading drop key only, an abuse report that deletes only that object, per-key quotas, per-IP (IPv6 per /64) and global limits, and no logs: aggregate counters only.@agentlink/cryptogains the drop request signature (verification and the unsigned event), the drop URL MAC and an incremental SHA-256, with thedrop-authvectors in all runtimes. Spec (07 §5.4, 04 §4, 08 §7; proposed, owner confirms at merge): the signature format, the signed URLs, the limits with the shared-egress and global-cap rules, the download limit, and the CORS policy for browser downloads. - Encrypted drop deploy pipeline (M1-27b, S2, D-48):
apps/drop/wrangler.toml(staging on workers.dev, production ondrop.agentlink.4thtech.io; bucket and Durable Object bindings, a raised CPU limit, and observability, Logpush and Tail Workers off) and.github/workflows/deploy-drop.yml: the bundle is built once, deployed to staging and smoke-tested there (signed upload, download and delete; an expired download refused), then deployed to production automatically once staging is green, with a health check, automatic rollback, a read-back of the 7-day bucket lifecycle rule and of the logging settings, a freeze on failure, and a staging rollback rehearsal. It stays inactive until the owner sets the Cloudflare tokens and the Worker secrets. Process:release-and-deployment.md§1.1, §2, §4.5.1, §6, §8 andci-cd.md; the G-40 assumption is recorded in §4.5.1. - The storage adapter is now tested against a real S3 server on every pull request (a MinIO container over TLS) and every day against a Cloudflare R2 test bucket (M1-6a-2, S1, sensitive path): upload, a link for the recipient capped at 7 days, download through it, expiry and delete, with a check that no link, key or credential is ever logged.
- Large files can be uploaded to your own S3-compatible storage in parts (M1-6b part 1, S2, 07 §5.2): an interrupted or failed upload is always cancelled, so nothing half-uploaded is left in your bucket, and leftovers from a crash can be cleaned up. A storage safety net can also be set: a bucket rule that deletes AgentLink files after 30 days and cancels any AgentLink upload left unfinished for a day, added next to any rules you already have, never replacing them. The recommended storage key now also includes the two permissions needed to cancel and clean up unfinished uploads (07 §5.2). Encrypting large files on the fly and using this for sending files follow in part 2.
- Large files are encrypted on the fly as they upload to your own S3-compatible storage, and decrypted on the fly as they download, so a large file never has to fit in memory (M1-6b part 2, S2, 07 §5.2, 04 §4): every file gets its own fresh key; a download stops as soon as it is larger than the file it claims to be; and a downloaded file appears only once all of it has been checked, so a damaged or tampered download never leaves a partial file behind. Using this for sending and receiving files comes with the storage selection and file tools.
- Owner approvals (M1-8e, S2, 04 §2.3.1, 08 §1): the local approval page on
http://localhost:<port>can set up a passkey (Touch ID, Face ID or Windows Hello) with one entry of your owner code, and then approve or deny with that passkey instead of typing the code. Your owner code keeps working; a passkey without the PRF feature is not stored. The spec now names where the passkey copy of the owner key lives (owner-passkey, one per computer user for now) and the checks on every passkey answer (proposed, owner confirms at merge). The page is not opened by the CLI yet. - CLI (M1-9z, S2, 04 §2.8):
agentlink owner change-code† replaces an exposed owner code with a new generated one. It asks for the current owner code (three tries), shows the new code once and asks for its last four characters, then re-wraps the same owner key: the owner fingerprint and every agent card stay valid. The new wrap is staged and checked before the saved owner key is replaced, so a failure leaves the current code working. Refused in a terminal an AI app controls; a recovery kit is not changed. - Owner commands
agentlink contacts rename <contactId> <name>andagentlink contacts remove <contactId>(M1-9za, S2, 04 §2.5.1, 06 §8): fix two contacts that show the same name (AMBIGUOUS_CONTACT) by giving one your own name or removing the one you no longer need. Both need your owner code in a terminal you opened; your AI can't do either, and no AI tool was added.agentlink contactsnow lists each contact's ID. Spec: new owner-action opscontact.renameandcontact.remove(proposed, owner confirms at merge). - A nightly two-machine test (OPS-CI-7, S2, process): two real agents on a Mac and a Linux runner pair, message, resist a hostile note and agree terms over the default relays. It is not part of the merge gate, runs only when
mainhas changed, and uses the cheapest model.
Changed
-
Spec (G-26, G-20, S2, D-80, D-81): ALF1 file encryption now binds the 17-byte header into every chunk's AAD (
header ‖ u8(isFinal), 04 §4), so a changed header fails decryption. OCCRegistry gets an owner-signed EIP-712Revocationstruct with single-use owner nonces shared withRegistration, an epoch check, and revoke while paused (05 §4). Spec only; no code change. -
CI (OPS-CI-3, S2, process):
pr-hygieneaccepts task IDs of the formOPS-<WORD>-<n>(for exampleOPS-CI-3) in PR titles, and thechangesetscheck no longer asks for an empty changeset when a PR touches only the private tooling packages (tools/,e2e/); any other workspace package still requires one. -
CLI (M1-22-b, S1):
agentlink_inboxoffers carry atermsPartiesfield naming the agents in their terms as "you", a contact's name (06 §2.1 rule) or null, so summaries need no raw addresses; withopenOffers: true, identical offers from one contact (same terms hash) add one hint line (" sent the same offer N times; reply once."). The offers themselves are unchanged. Spec: 06 §2.2 documentstermsParties. -
Core (M1-7i, S2): the policy engine applies the remaining 06 §3 rules. Files: a send needs an owner-allowed folder (none by default, so the tool asks for setup), stays within
maxSendSizeMB, and goes inline, to the encrypted drop or to own storage perfiles.storage(D-48); a received file stays withinmaxReceiveSizeMB. Proofs:notarize,perActionMaxUSDanddailyBudgetUSDover a rolling 24 h window (G-7), with per-contact rules (G-32) in the 06 §3 steps. The budget check and the reservation of the maximum fee run in one store transaction, a reservation is released only after its deadline with no record on-chain, and a missing, inconsistent or clock-skewed ledger makes every paid proof ask the owner until the next owner-signed policy. Under an invalid policy, sending a file is refused (no folder is allowed) and fetching one needs the owner. -
CI (M1-9c-c, S2, process): new
bundle-checkjob inci-gate. It builds the CLI on a runner with no keychain and no native-module build and fails when the publisheddist/agentlink.jsimports a crypto-capable MCP SDK transitive package (jose,pkce-challenge,cookie-signature,etag,hono), an SDK subpath outside the stdio set or an undeclared package, or whensrc/tools/bundled for a neutral (Worker) platform reaches a Node built-in,@agentlink/nodeor the MCP SDK (IR-M1-9c-1 F07, D-76, 06 §1). No new dependency: it reuses the repository's Vite.docs/process/ci-cd.mdrecords it. -
Spec (M1-9v, S1, 06 §4): the untrusted-content wrapper spec now says that every opening and closing
untrusted_contenttag inside content is escaped, look-alike forms included, as the code already does. No behaviour change. -
agentlink_propose_termsandagentlink_respond_to_termsnow passclientRequestIdto the core (M1-32-c, S1): a repeated call returns the original result and sends nothing more; a different request under the same key isIDEMPOTENCY_KEY_REUSED, a concurrent repeat isIN_PROGRESS(asagentlink_send), and a repeat whose approval has since been decided reports that approval's state. -
CLI: an agent that has no card from its owner yet is now told so.
agentlink_status, the other tools andagentlink doctorsay to runagentlink owner add-agent --agent <name>in your own terminal instead of suggestinginit(M1-9u).owner add-agentalso explains the case where an owner exists but no agent here is attached to it. -
OPS-REL-3: the "Version packages" PR now rolls
CHANGELOG.md[Unreleased]into the version section (tools/release/roll-changelog.mjs), and a GitHub Release body over 125,000 characters falls back to a short note with a link. -
CI (OPS-CI-4, S2, process): new
sync-pinjob insecurity.yml(push tomain, weekly) fails when the secret-scan stub on thesyncbranch is pinned to a commit older than the last change tosecret-scan.ymlor.gitleaks.tomlonmain;sync-protocol.mdandci-cd.mddocument the re-pin procedure. -
Local approval page on
http://localhost:<port>(M1-31, S2, D-79; 08 §1, 04 §2.5, 06 §2.5, 11 §6.2.1; owner decision 2026-10-17): WebAuthn does not accept an IP address as an RP ID, so the page moves from127.0.0.1tolocalhostfor the passkey (M1-8e). The server listens on127.0.0.1and::1(IPv6-only) on the same random port and nowhere else; if another program holds::1on that port it tries a new one (at most 5), and a machine with no IPv6 loopback gets127.0.0.1only. OnlyHost: localhost:<port>andOrigin: http://localhost:<port>are accepted;127.0.0.1:<port>is now refused. The OAuth loopback redirect stays on127.0.0.1. -
Process (owner decision, 2026-10-17): open question G-14 (
agentlink_fetch_fileand SSRF in Workers) blocks only M2-3b, not M1-15; the Workers egress part stays open. -
Docs: README,
knowledge/,llms.txt,docs-site/anddocs/status.yamlnow say v0.1.0 is released (16 Oct 2026,npx -y @4thtech-io/agentlink init); the OpenCode and Antigravity quickstart, the messages, contacts, approvals, first-message and troubleshooting pages are filled in. Files, proof and the apps stay planned. -
Docs (DOCS-DELIVERY, S1): a plain-language page, "How messages are delivered", on live and mailbox delivery, the status-first habit and the limits (relay retention, look-back); live delivery is marked as shipping in the next release.
-
Docs (DOCS-KNOW, S1):
knowledge/is consolidated from eleven numbered files intoagentlink-overview.md,agentlink-userguide.md,agentlink-security-and-privacy.mdandagentlink-technical.md, following the pattern of The Curator'sllm-docs/;knowledge/README.mdstates the maintenance rule (update on every valuable or user-visible change).llms.txt, the generated status target, the link check and the docs-pass steps follow. No feature is newly claimed as available. -
Docs updated for M1b progress (DOCS, S0): the README Status and Roadmap, the docs-site status page, the knowledge base and the backlog card statuses now show M1b in progress and separate what is merged from what is released (npm
latestis still 0.1.0); the Claude Desktop and Node.js 22.14 notes say which release they ship in. -
Spec gap G-10 resolved (D-83, S1): the contracts ABI package
@4thtech-io/occ-v2-abiis a private in-repo package bundled into the CLI and is not published to npm; card M1-3f updated. -
Spec (M1-1d, 04 §4): a file key is 32 fresh random bytes per file and is never reused for another file.
-
Spec (M1-1i-b, proposed, owner confirms at merge): 04 §4 fixes the ALF1 version byte (
0x01), the chunking at chunk boundaries (a full final chunk; an empty one only for an empty file) and the chunk size (senders 1 MiB; recipients accept 1 B to 16 MiB); 05 §4 and 04 §2.4.1 define theRevocationJSON form. -
A streaming download may decrypt into a private hidden temporary file and move it into place only once the whole file and both of its fingerprints check out; it is removed on any failure and after a crash (D-84, 04 §4, M1-6b part 2).
-
Owner setup (M1-8h-b, S2,
@agentlink/node; IR-M1-8h-1 Lows):owner initasks for the agent's name for contacts before the owner code is shown (Enter keeps the local agent name; up to 32 letters, digits, spaces, dots, apostrophes or hyphens in one script, accepted only if the contact-name rule shows it unchanged, so contacts see it as self-declared). If the first card fails, the rollback removes the owner key only while it is still the one this run stored, so a concurrent run's owner key is never deleted. The generic owner operation no longer signs agent cards at all; cards are signed only in the bound sessions ofowner initandowner add-agent. -
The approval for connecting with a new contact now names who is inviting: their self-declared name (clearly labelled) and their owner fingerprint, so you can tell who you are connecting to (M1-9zb, S1, 06 §2.1).
-
The deterministic e2e suite is now part of the merge gate (OPS-CI-6, S1, process):
ci.ymlcalls it andci-gaterequires it, in pull requests and in the merge queue; docs-only changes skip it. -
Changelog fragments (OPS-REL-4, S2, process): a PR now adds
changelog.d/<card-id>.mdinstead of a line underCHANGELOG.md[Unreleased], so parallel PRs stop conflicting in the merge queue. The release roll (tools/release/roll-changelog.mjs) folds the fragments and any legacy[Unreleased]lines into the version section and deletes them; a new CI step fails a user-visible, spec or process PR with no entry and warns on a direct[Unreleased]edit. Format inchangelog.d/README.md.
Fixed
- CLI (M1-9w, S1): Gemini CLI is detected by the
geminiprogram or~/.gemini/settings.json, so a computer with only Antigravity's~/.gemini/config/mcp_config.jsonis offered Antigravity, not Gemini CLI. agentlink owner add-agent --agent <name>(M1-9x, S2): when an owner is set up on this computer but no agent has a card from it (for example after every agent was removed), it no longer dead-ends betweenowner initandowner add-agent. Your owner code unlocks the saved owner, you confirm the owner fingerprint, and the agent gets its card. Same rules as before: your own terminal only, refused inside an AI app, the owner key is never changed.- Spec 04 §2.8 (M1-9x, S2, proposed, owner confirms at merge): new rule "No local card names the owner" (owner keys derived from the unlocked owner item, fingerprint confirmed before the one card is signed, a consistency check, not authentication); "Expected owner" names this case.
- Windows file-path checks (M1-15-b, S2, 04 §4): the allowed-folder tests now run on Windows with no skipped test. Paths written with 8.3 short names are compared in their long form on both sides, a folder swapped for a link is refused check→open on every OS, and post-open where the OS allows it, and a Windows named pipe is refused before it is opened. No change to what is allowed or refused.
- CLI (M1-22-b, S1, 06 §2.2):
agentlink_inboxfailed with an output-schema error for any message that carried terms, because the newtermsPartiesfield was not declared in the tool's output schema. It is declared now, and the inbox, status and contacts tests validate their results against the declared schema. - Dogfood script reports tool error codes (for example AMBIGUOUS_CONTACT) in the step notes, stops before the run when two contacts share the peer agent name, and addresses the peer by contact ID (M1-23-c, S1).
- Accepting terms sometimes failed with an internal error instead of asking the owner (M1-32-d, S2, 04 §3.4.2): the approval request read the clock a second time, so a second boundary after the commitment scope was built made the request invalid. The request now starts at the scope's
createdAt; a scope that expires while it is asked is reported as expired terms. - CLI (M1-9y, S1):
agentlinkneeds Node.js 22.14 or newer (was 22.12). On an older Node it prints one line ("AgentLink needs Node.js 22.14 or newer; you have vX. Update Node ...") on stderr and exits, instead of crashing with a segmentation fault (seen on 22.13.1); this includesagentlink mcp, which never writes it to stdout. One-shot commands likeowner initno longer print raw JSON info log lines;AGENTLINK_LOG=info|debugbrings them back. - CI (OPS-CI-5, S2): a required
path-guardjob runs the Windows and POSIX path-guard tests on macOS and Windows runners and fails if they were skipped or did not run.