)]}'
{
  "log": [
    {
      "commit": "8c0a876339554b9d4da09f5ac368889d1e042663",
      "tree": "c65bc2a90aa6dd60ee8e2ddbeed4bbddd9f527af",
      "parents": [
        "ff61b5ec20dd6b41abf75e5bde887e404254197b"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Nov 03 14:47:17 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Nov 03 14:47:17 2026 +0800"
      },
      "message": "chore: disable Dependabot automatic security update PRs (#100)\n\nSet github.dependabot_updates to false in .asf.yaml so Dependabot no\nlonger opens automatic security update pull requests. Security alerts\nare fixed through regular dependency-bump PRs instead.\n\nDependabot alerts are not changed and stay enabled.\n\nGenerated-by: Kiro Crew (claude-opus-5.5)"
    },
    {
      "commit": "ff61b5ec20dd6b41abf75e5bde887e404254197b",
      "tree": "9ff5ec4d832e2ffe73cfd3c044f32ced98b2c9fc",
      "parents": [
        "cf6bd7afe1d1a625b0d85096666a6ba0986a223a"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Nov 03 09:09:25 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Nov 03 09:09:25 2026 +0800"
      },
      "message": "Bump dependencies to fix security alerts (#98)\n\n- golang.org/x/net v0.58.0 -\u003e v0.62.0 (indirect, through gRPC), with\n  golang.org/x/sys v0.47.0 -\u003e v0.49.0 and golang.org/x/text v0.41.0 -\u003e\n  v0.43.0. OSV lists x/net 0.58.0 for GO-2026-6603, GO-2026-6610,\n  GO-2026-6611, GO-2026-6612, GO-2026-6617, GO-2026-6860 and\n  GO-2026-6866; govulncheck finds none of them reachable from asz.\n- dist-material/LICENSE regenerated with `make dep-licenses`; only the\n  three version lines change. Changelog updated under Release.\n\nGitHub Actions: pnpm/action-setup v6.0.10 (expires on the ASF\nallowlist 2026-12-01) -\u003e v6.1.0 (ea17c68), actions/checkout v4 -\u003e v7,\nactions/setup-go v5 -\u003e v7, actions/setup-python v5 -\u003e v7,\nactions/setup-node v4 -\u003e v7, actions/upload-artifact v4 -\u003e v7,\nactions/download-artifact v4 -\u003e v8."
    },
    {
      "commit": "cf6bd7afe1d1a625b0d85096666a6ba0986a223a",
      "tree": "e94cf4e8234dd0ee1283c997d38cded78ec338c0",
      "parents": [
        "19c5a8939a61d0b342f6f8295821f10dcf230c63"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Nov 02 23:30:46 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 23:30:46 2026 +0800"
      },
      "message": "Judge in asz server: a conversation nothing was collected for is judged again from where it changed (#97)\n\nWith judge.server.enabled on, asz server judges after each watching pass,\none conversation at a time: one nothing has been collected for in\njudge.server.idle_after, at least 10 minutes, whose collection and parse\nwere complete, and which worked outside the storage root. At most\njudge.server.runs_per_day runs are admitted in any 24 hours, and the\nserver does not start the policy without judge.max_steps and\njudge.run_timeout, since nobody watches a run it starts.\n\nA conversation is judged again each time it goes on and goes quiet. What\nis due is decided per main talk by its view digest: what a run would be\nshown of the talk now, against what the last automatic run that judged\nit was shown. A window runs from the first due talk to the last, cut by\njudge.max_steps into parts; a single talk over the limit is skipped. Each\nautomatic run, one admitted or one refused for its size, leaves a\nnumbered record in _judge/runs/. It is written before an admitted run\u0027s\ndirectory is reserved, so no run exists without one, and the run\u0027s own\nrecord in its directory names it back. That record, admission.json until\nnow, is renamed run.json. A run that did not close complete covers\nnothing and is tried again only once something new lands. After an\nupgrade that changes the chain\u0027s interpretation, a talk judged before it\nis judged again only when a record of its own lands.\n\nThe local collectors now say, of every session they read, whether all of\nit was read to its end: a line still being written, a held Kiro prompt, a\nbusy session, a conflict or a body still being written leaves it\nincomplete. Each source keeps its last outcome per session. Kiro\u0027s origin\nis read from the live source, and from the landed header when the source\nis gone. A window\u0027s end takes in the change and execution records of the\nwork it selects.\n\n/api/status gains a judge object and the list page a line. A stop halts\nadmissions at once, lets a running pass finish, then gives the run going\nits bounded cleanup."
    },
    {
      "commit": "19c5a8939a61d0b342f6f8295821f10dcf230c63",
      "tree": "a3c0b7ae43d70e76983372b3187ee45c97c7b73d",
      "parents": [
        "07a2d95fdf9ebd8af1efc06fff6b1c40d5d02e4d"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Nov 02 14:29:33 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Nov 02 14:29:33 2026 +0800"
      },
      "message": "Judge: admit each run in one decision, hold it active against retention, and bound a run\u0027s size and time (#96)\n\nA judge run is admitted under the root lock in one decision: it is refused\nfor a retired session, for one whose files a removal has not finished with,\nand while another run or regroup of the conversation is active. The run\u0027s\ndirectory, its activity lock `.active` and `admission.json` are made before\nthe lock is released. Retention probes `.active` at both levels and in both\nrecoveries, and removes nothing of a session while it is held. A regroup\ntakes the run\u0027s lock before it reads a stored file. `asz judge -list` says\nwhether each run is closed, running, stopped or unknown, and who started it.\n\nThe judge\u0027s storage writes wait for the root until their context ends,\ninstead of failing after 30 seconds while a pass holds it. One gate per root\nin the process makes waiters take turns. A run\u0027s work ends at an interrupt or\nat `judge.run_timeout`; then no evaluator call starts, and the stages that\nfinished are published under a cleanup context bounded to two minutes.\n\n`judge.max_steps` refuses a run that would judge more steps before anything\nis made. Both limits are off by default, and `-max-steps` and `-run-timeout`\nset them for one command. `max_turns` bounds codex and kiro-cli through the\ntool calls of their navigation server, since neither has an option of its own.\n\nasz never collects its own evaluators\u0027 sessions: kiro-local leaves out any\nsession with a workspace inside the storage root, links resolved and an\nunresolved path never taken for outside, and an agent CLI runs without\nOTEL_LOG_RAW_API_BODIES. Judge runs are exported only when\nexport.otlp.judgments is on."
    },
    {
      "commit": "07a2d95fdf9ebd8af1efc06fff6b1c40d5d02e4d",
      "tree": "0e8c3739be9d6c5d44ed26db6826c9a966060198",
      "parents": [
        "341f8c0c9cb8245669cbe45ff5393f6972e05616"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Wed Oct 28 20:10:51 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 20:10:51 2026 +0800"
      },
      "message": "Judge: goals only group targets, and -show prints a report as text, Markdown or a page (#95)\n\nA goal only groups targets. The summary each goal carried (its main target, worst class, first\nrecorded finding and scores) is removed. It repeated what the targets already say.\n\n`-labels` is renamed `-goals`. A regroup\u0027s header key is `goals`, and its frames are `goals_text`\nand `goal`. One type, `Goal`, holds the judge\u0027s goals and a person\u0027s.\n\n`-show` prints the latest closed run, and `-run N` names another. The text is a tree, goal by goal:\neach target\u0027s status and why, its score, its criteria, what was done and what is missing. It ends\nwith the overall assessment and the overall result. It has no ids and no step addresses.\n`-format markdown` prints the key conclusions only, with each web address written as code, exactly\nas it stands. `-format html` prints one page that opens as a local file with no server. It uses\nHorizon\u0027s design and has a table of targets, each with a button to its details. Each target is\nfolded to its status and score. A cited step opens in a dialog. The steps are read at the round the\nrun judged and withheld as `view.hide` says. A conversation pulled from another asz shows no steps.\n\nThe navigation server writes its log\u0027s closing line when the agent CLI ends it with SIGINT or\nSIGTERM, as Claude Code 2.1.312 does, after the answer being given is written and logged."
    },
    {
      "commit": "341f8c0c9cb8245669cbe45ff5393f6972e05616",
      "tree": "a9cb36f2e336a466c96dc1b5fac4205c9dfb2e53",
      "parents": [
        "4ff3d426780d28ef06bced7050d60426e9cebbff"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Wed Oct 28 09:09:10 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 09:09:10 2026 +0800"
      },
      "message": "Judge: say how much of its scope each absence claim was given, and fetch references through MCP (#94)\n\nA claim that something did not happen, a verdict of not observed, a question\nnever asked, an assumption never checked, or a person\u0027s goal found absent,\nstays the evaluator\u0027s judgment. Beside it asz now reports how much of its\nscope the evaluator was given, in steps: opened, only listed, not given, and\nthose whose evidence no one can be given. The counts come from what each\nanswer returned, counted once it was given; a navigation log that is not\nwhole makes them unavailable, never smaller.\n\nThe two divergence causes are renamed question_not_asked and\nassumption_not_checked. A goal\u0027s first finding is its first recorded one,\nchosen by one function for the close and -show. A person\u0027s goal is absent\nonly when every target was placed. A tool call that shares a record with the\nagent\u0027s text reads as its arguments in asz.view.\n\nA judge reference can be fetched by one tool of an MCP server, remote over\nStreamable HTTP or local over standard input and output, speaking revision\n2026-07-28 and the four revisions before it. asz judge -reference-tools lists\na server\u0027s tools. A URL is kept without its query, a server without its path,\nand a failed fetch as a class and a detail asz builds. The secrets of a fetch\nare removed from all it keeps, as written and through every JSON spelling.\n\nTests that replace a file write the new file before they remove the old one.\nOn a newer Linux runner, a removed file\u0027s inode went to the next file made,\nso nothing was replaced and the identity checks had nothing to find."
    },
    {
      "commit": "4ff3d426780d28ef06bced7050d60426e9cebbff",
      "tree": "ae1f824744f73d58f6caf5344f206e303b7cdbd4",
      "parents": [
        "9190944c732bbdc9ef5580a262c5e3d292fe4090"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Oct 27 16:58:22 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 16:58:22 2026 +0800"
      },
      "message": "Pull conversations from another asz, to judge and read them here (#92)\n\nAn asz that collects a product\u0027s agents now serves its conversations to\nother asz installations, and asz pull copies the ones a person picks.\n\n- asz view and asz server answer pulls when remote_server.tokens_file is\n  set: every request on the address needs a token, the page too, with\n  optional TLS and client certificates. asz token create makes a token;\n  removing its line revokes it.\n- The server freezes a conversation\u0027s head into a snapshot it serves under\n  a lease, and sends the files a client lacks as one tar.gz archive,\n  fetched in ranges that resume where a connection broke.\n- asz pull -list lists what each remote holds; asz pull REMOTE/CONVERSATION\n  keeps each pull as an immutable snapshot under _remote/, reusing what the\n  newest valid snapshot holds and checking the evidence against the\n  digests its rounds carry before it publishes. Snapshots expire after\n  remote_snapshot_ttl.\n- asz judge -remote-conversation judges a pulled snapshot, and -pull pulls\n  first. asz view lists pulled conversations beside the root\u0027s own, each\n  title starting with [remote, NAME].\n- Every write is built privately, synced and published by a rename, and an\n  interrupted pull, sweep or server transition is recovered by the next one.\n  A durability model tests a power loss at each step.\n- The protocol is docs/en/formats/asz-pull.md; setup, with a deployment\n  diagram, is docs/en/setup/remote-pull.md. An end-to-end test runs servers\n  in Docker Compose with TLS and tokens (make e2e-pull)."
    },
    {
      "commit": "9190944c732bbdc9ef5580a262c5e3d292fe4090",
      "tree": "6fb0d4b385a11e1de6566509ea2482c474f65491",
      "parents": [
        "8ed175d0945644d6c4241771eecf8afecb77fc4f"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Oct 26 15:41:02 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 15:41:02 2026 +0800"
      },
      "message": "Write each paragraph of the GitHub release text on one line (#91)\n\nA GitHub release draws every line break as a break. changes.md wraps\nits lines, which a Markdown page reads as one paragraph, so the release\npages broke sentences in the middle: 662 times on 0.6.0, and 351, 147\nand 88 times on 0.3.0, 0.4.0 and 0.5.0.\n\npublish now joins the lines of each paragraph and list item. A blank\nline and a new list item start a new line. Headings, tables, quotes,\nHTML, rules, fenced code and hard breaks stay as they are. The test\u0027s\nchangelog has each of these, and fails with the old text."
    },
    {
      "commit": "8ed175d0945644d6c4241771eecf8afecb77fc4f",
      "tree": "dc212c4b36c6a971dc4eb3fcea1b32120061a165",
      "parents": [
        "87f6bfe38cfb09d57b0be3368b6eddef8e9dfcf4"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Oct 26 10:40:46 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 10:40:46 2026 +0800"
      },
      "message": "Remove Scoop and winget from the release process (#90)\n\nNo version was submitted to either, and none is planned. The release\nguide loses their sections, its target and step rows, and the Scoop\ncondition of --remove-old. publish no longer prints them as a next step,\nand its --remove-old message no longer waits for a Scoop bucket.\n\nThe guide keeps the rule that a package manager the project is not in\nyet needs the PMC\u0027s agreement first."
    },
    {
      "commit": "87f6bfe38cfb09d57b0be3368b6eddef8e9dfcf4",
      "tree": "a173adcdef8e88657a786f192c814585d77ce5aa",
      "parents": [
        "80656aa26c5ca95ae9c52c7ae92ca61d4eda26f6"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 25 23:35:59 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 25 23:35:59 2026 +0800"
      },
      "message": "Homebrew formulae for 0.6.0 (#89)"
    },
    {
      "commit": "80656aa26c5ca95ae9c52c7ae92ca61d4eda26f6",
      "tree": "6143438dfcec899363ed2d5d606072ee53f0b92b",
      "parents": [
        "e714575d682cf793270534e537790c305e493b1f"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Oct 23 17:53:45 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 23 17:53:45 2026 +0800"
      },
      "message": "Report a judge run by goal, and compare it with what a person concluded (#88)\n\nA judge run now says what the conversation was for, whether each target\nwas reached, why asz believes it, and what the agent did for it.\n\n- The targets stage says how each input relates to the inputs before it,\n  and groups targets into goals, each with how its request changed and a\n  main target, or undetermined.\n- The achievement stage records each target\u0027s deliveries, undoings and\n  deliveries again, the contradictions tied to them, its rounds of rework,\n  findings anchored where a problem happened, and an account of what was\n  done: what was checked for a question, the file operations for a change,\n  and the requests to another system. Excerpts are checked against the\n  record.\n- Each target ends in a class, with the record\u0027s score and a final score\n  that applies what contradicts or undoes the deliveries standing at the\n  end. Rework is priced by how its calls were labeled.\n- File operations come from change records, and from the requests of\n  editing tools, which each runtime\u0027s adapter reads.\n- asz judge -reference saves each source before any evaluator runs. A\n  reference stage finds the conclusions it holds, and a divergence stage\n  compares the conversation with each; a contradiction or a rollback of a\n  standing delivery changes the final score.\n- asz judge -labels and -regroup compare the judge\u0027s grouping with a\n  person\u0027s goals, a plain list in their own words: one evaluator call\n  places each target in the goal it serves, and asz classes each goal,\n  in a versioned file beside the run.\n- asz judge -show prints all of it goal by goal, the most severe first.\n- CI runs on ubuntu-26.04, and ubuntu-26.04-arm, rather than ubuntu-latest,\n  so a new image never changes what CI runs on without a change here."
    },
    {
      "commit": "e714575d682cf793270534e537790c305e493b1f",
      "tree": "ab0c027b20e14f5ca15b562d82d03cd660dd057a",
      "parents": [
        "eed643083ca57c02fa53b661ae51ffb0776ca5bf"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Oct 23 07:18:05 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 23 07:18:05 2026 +0800"
      },
      "message": "Judge whether a conversation achieved its targets, and what it cost (#87)\n\nFor each target the person set in a conversation, the judge says whether\nit was achieved and how efficiently the time and tokens were spent.\n\nasz measure prints what a conversation\u0027s model calls consumed: per talk,\nwith the child agents each one started, at any depth, priced where an\noptional price file names the model. It calls no model.\n\nasz judge runs a plan of stages over one conversation, or a window of\nit, and writes one verified file per stage:\n\n- ledger, computed: every model call counted once, and its owner.\n- targets: the targets the person set, and what each input did to them.\n- achievement: whether each target was achieved, criterion by criterion,\n  with citations.\n- subtasks: for each sub-agent, its outcome, whether its report was true,\n  whether it was used, and whether giving it the task was reasonable. A\n  task given wrong and an untrue report acted on are charged to whoever\n  caused them.\n- efficiency: which parts of the agent\u0027s own work were avoidable.\n- close, computed: each target\u0027s final statement, costs, and two scores,\n  achievement and efficiency, which are never combined.\n\nAn evaluator reads the conversation only through a bounded navigation,\nserved as an MCP server to agent CLIs and in process to model APIs.\nEvery answer fits a page, every call has a reading budget, and nothing\npast the run\u0027s head or window is shown. asz checks every answer against\nthe record: a decision with no valid evidence falls back to the value\nthat claims nothing, and the stage file says what was weakened. What\njudging consumes is counted per model request as the provider reported\nit, never estimated.\n\nJudge runs go to the OTLP receiver beside the conversation files. They\nare removed with their session, and scenario removal waits until they\nare sent. The sj/1 file format is documented in\ndocs/en/formats/session-judgment.md."
    },
    {
      "commit": "eed643083ca57c02fa53b661ae51ffb0776ca5bf",
      "tree": "a5ecda7e4a78c172f82e4312e77eafa515b03734",
      "parents": [
        "928b67aaa030e0ed212be726077b10e060c60a76"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Oct 20 07:04:15 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 20 07:04:15 2026 +0800"
      },
      "message": "Prepare the 0.6.0 candidate and open 0.7.0 (#86)\n\n* Prepare the 0.6.0 candidate\n\nThe changelog of 0.6.0 loses its in-development note and keeps its\npath, docs/en/changes/changes.md, which the menu and the welcome page\nlink, and the Python plugin\u0027s version is 0.6.0. The tag goes on this\ncommit, as the candidate for the vote.\n\n* Open 0.7.0\n\nThe changelog of 0.6.0 moves from changes.md to its own page,\nchanges-0.6.0.md, and the version is listed under Changelog, right\nafter Current Version. changes.md is the changelog of 0.7.0 from here.\nCurrent Version and the welcome page still link changes.md."
    },
    {
      "commit": "928b67aaa030e0ed212be726077b10e060c60a76",
      "tree": "1c2d39c1e25e46655d585d43a668f250d0da9aa4",
      "parents": [
        "0634abfeb247f642d5e26349dc283d93a7a14ad4"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 23:49:38 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 23:49:38 2026 +0800"
      },
      "message": "Read a LangChain JS message by its class on the page (#85)\n\nThe embedded renderer moves to Horizon 64c6948\n(apache/skywalking-horizon-ui#203). LangChain for JavaScript names a\nmessage\u0027s kind only by its class, not in its fields, so the Prompt tab\nshowed every message of such a call as \"message\", and a tool result as\nplain text without the call it answers. The renderer now takes the kind\nfrom the class when the fields carry none, and reads a streamed chunk as\nthe message it belongs to."
    },
    {
      "commit": "0634abfeb247f642d5e26349dc283d93a7a14ad4",
      "tree": "0f4152d91f87364436a2a17726796c868697bee1",
      "parents": [
        "c97b21983dd36ea9f0301c8353c77ec512d08065"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 23:23:45 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 23:23:45 2026 +0800"
      },
      "message": "Parse what landed past the index, and close four retention and page gaps (#82)\n\nRecords landed just before a crash, or before a failed save of the index,\nwere never parsed when the runtime deleted the session\u0027s source before the\nnext pass, as Kiro Crew deletes a session and Claude Code prunes its files.\nA collector brought the index up to the landed files only while it read the\nsource, and parse trusted indexed_seq and rebuilt only a missing index. So\nthose records never reached the conversation, and retention never removed\nthe files or retired the session. Parse now indexes the landed files above\nindexed_seq before it cuts a round, for every adapter, through\nindex.LoadFor as every writer of the index does.\n\nA retirement deleted the conversation directory named by the session\u0027s id\nwithout reading it, while the push sends each round by the session its\nheader names. It now removes that directory only when its head round names\nthe session, or it has no round.\n\nThe page kept a conversation for the chain head and the removal generation\nonly. After a session\u0027s directory was removed by hand, or a retirement\nstopped between the session\u0027s directory and its chain, it served the text\nof files that were gone and a document that said verified. It now draws the\nconversation again when the session\u0027s landed files change.\n\nAn empty file under a round\u0027s name, the reservation a writer leaves on a\nfilesystem without an exclusive rename, made the push fail on every pass,\nmade retention and retirement fail when it sorted as the head, and held a\nscenario removal for ever. These readers now pass over it, as the chain\ndoes, and the Session Flow page says a reader must.\n\nThe Claude Code, Kiro and LangChain glossaries gave 64 terms where asz\nglossary prints 66. The totals are corrected, and a test checks each page\u0027s\ntotals and every row against the adapter\u0027s declarations.\n\nThe chain tests take most of go test\u0027s default ten minutes under the race\ndetector, and this change and others add to them, so make test and the CI\nsteps that run ./tests/... name a limit of 30 minutes."
    },
    {
      "commit": "c97b21983dd36ea9f0301c8353c77ec512d08065",
      "tree": "24a925fe3e82f7ab64935710d3a163d49a473b88",
      "parents": [
        "57444f11e7d8e6c757bd1c90f424459683762b10"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 22:40:28 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 22:40:28 2026 +0800"
      },
      "message": "Fix five findings of the 0.6.0 review in collection, metrics and the plugin (#84)\n\nThe plugin removes an output file it has not written to for retention.ttl,\nand writes a new one at the same path when the session goes on. The changes\nadapter read the new file against the old cursor. That was a truncation or\ndigest conflict, and the conflict is kept, so nothing more from the path was\ncollected, its change records and MCP execution records included. The cursor\nof a plugin file now records head_sha256, the digest of the file\u0027s first\nline, read on every pass. A file that begins with another line is a later\nfile, read from its start behind a cursor named for the path and the earlier\nfirst line, also when it has the old inode and the old length. A file\nrewritten behind its cursor still begins with its first line and is still a\nconflict. A cursor that read lines before 0.6.0 has no head and is given\nnone.\n\nA later file, and the same path under a second recorder root, lands under a\nsrc of its own: the path, #, and the start of its first line\u0027s digest, and\nits cursor\u0027s source names it. asz verify checked a stream\u0027s changes or\nexecution files as one source against \u003ckind\u003e.cursor alone, so a second\nsource read as lines landed twice and as byte gaps, and a lost file of it\nwas not found. It now checks each src apart against its own cursor, through\nskeletons too. The cursor names live in storage beside the second source\u0027s:\nLaterCursorName for a later file, MarkedSource and SourcePath for the mark.\nasz repack writes a relative origin again for the new root and renames the\ncursors named for it; it now renames a later file\u0027s cursor too, and reads a\nmarked cursor\u0027s path without its mark, or collection into the new root read\nthose files from their start.\n\nThe token metric read on into the next landed file only until the first\nrecord of no call, such as a tool result. A call\u0027s own tool results sit\nbetween its fragments, so a main-stream call cut at a file boundary counted\nin an earlier fragment\u0027s minute, and at the look-back edge it could be left\nout. A record of no call no longer stops the reading, a fragment of another\ncall does, and the file\u0027s last call is read on also when the file ends with\na tool result. Under a watching collector such a file waits the grace for\nits next file, as a file that ends at a fragment does.\n\nA tool result whose content held a text block and an image, with no\ntoolUseResult object, landed only the text. Content that holds anything but\ntext blocks now also travels whole as the part\u0027s data.\n\nThe plugin\u0027s scan suppressed a read error and recorded every file under a\ndirectory it could not read, or a file it could not read, as deleted, with\nthe record\u0027s coverage complete. Restoring access recorded them as created.\nSuch a path now keeps what the scan before saw, the step names it, and the\nrecord says coverage partial with a gap that names it. The stat cache also\ncompares the change time, which chmod moves, so a file made unreadable is\nread again and found so.\n\nThe plugin internals page named the plugin asz-changes and its data\ndirectory, run from a checkout, asz-changes-inline. The manifest names it\nfile-changes, so the directory is file-changes-inline."
    },
    {
      "commit": "57444f11e7d8e6c757bd1c90f424459683762b10",
      "tree": "c2a6d80c82a23986a633a4c9a230feae0ec3acd2",
      "parents": [
        "4b94c14f4f423d914e035048be55539a1e7847de"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 22:18:59 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 22:18:59 2026 +0800"
      },
      "message": "Fix what the LangChain receiver and plugin lost before 0.6.0 (#83)\n\nAn update written before its run\u0027s start in one request was completed\nonly from the arrivals before it. It had no kind, landed as a graph\u0027s own\nrun, and a model call\u0027s answer and response body were lost. Every arrival\nof a request is now remembered before any is completed. A run\u0027s arrivals\nin a request are read together, with the earlier start and the later end,\nso a failed tool is not joined to a call the model made after the tool\nfailed. A response body lands after its run\u0027s request, so its reply is\nread against that request instead of saying there was none.\n\nPATCH /runs/{id} with a body that carries no id was refused by the\nreceiver and by the collector. LangSmith JS 0.10.8 sends its updates so\nwhen it does not batch, so none of the runs it traced finished. The run is\nnow the one the path names, and a body that names another run is refused.\nThe receiver reads a single run by the collector\u0027s own rule.\n\nA chat model at the root of its trace holds its messages as a list per\nprompt. They were read as one flat list, so the question never opened the\nconversation, and with provider bodies off it was not landed. A model\nrun\u0027s messages are now read in both forms.\n\nA trace kept every run it ever saw while any run of it kept arriving, past\nthe day and the 50,000 runs that bound what is remembered, and every\nrequest saved them all again. A trace now forgets a run when the run is\nforgotten, and keeps the runs a remembered run ran inside.\n\nThe LangChain plugin filed a tool\u0027s changes under the project the\nenvironment names, while a tracer given a project in code sends the run\nto that project. It now watches LangChain\u0027s tracer start each tool run,\nkeeping the start a coroutine function when it is one, and reads the\nproject from the run. The begin, whose scan comes before the tool runs,\nis sent by whichever of the tracer and the plugin is told second, which\nLangChain does before the tool runs, and once sent no other begin is sent\nfor the run. A begin whose tracer never comes, as when the tool is\ncancelled between the two, is dropped after ten minutes. A tool no tracer\ntraces, or one traced with tracing turned off, records nothing. A tracer\nwhose start cannot be wrapped is left alone, and the plugin then files\nchanges under the environment\u0027s project.\n\nCI installs langchain-core and langsmith, at the versions the capture\nharness measured, before the shim tests, so the tests of which project a\ntraced run belongs to run there instead of skipping."
    },
    {
      "commit": "4b94c14f4f423d914e035048be55539a1e7847de",
      "tree": "ecbd14b992fa4a77b8cf8a2d990c22b10c15bc09",
      "parents": [
        "da56fb1fecaaed02f5130f776e810b2c703f96dc"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 22:05:55 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 22:05:55 2026 +0800"
      },
      "message": "Keep distinct ids apart, let a nested child return, reject blank round lines (#80)\n\nTwo different entities could get one id. A \"/\" inside a part was written\nas \"_\", so the tool ids \"a/b\" and \"a_b\" gave one node and one call took\nthe other\u0027s place. An id nested in another wrote its \"/\" as \"_\" too, so\ntalk/main/a and the talk of a child stream named main_a gave their runs\nof one cycle one id, and their in_segment relations too. And a reset\u0027s\nown id named the epoch it opens, so a reset whose id is \"0\" took the\nfirst epoch\u0027s id and followed itself, and one whose id is \"at_1_2\" took\nthe id of a reset with no id at sequence 1 row 2.\n\nNow a part has \"%\" written as \"%25\" and \"/\" as \"%2F\". An id nested in\nanother, a run\u0027s talk or a relation\u0027s two ends, has each \"%\" written as\n\"%25\", each \"_\" as \"%5F\" and then each \"/\" as \"_\", and is not escaped\nagain, so ids stay apart however deep they are nested. The epoch a\nreset with an id opens is epoch/\u003cstream\u003e/id/\u003cid\u003e, one with no id is\nepoch/\u003cstream\u003e/at/\u003cseq\u003e/\u003crow\u003e, and a segment is segment/at/\u003cseq\u003e/\u003crow\u003e.\nOf the 357,841 ids of 65 real conversations parsed from scratch, 3,637\nmoved: 2,791 starts and 28 result_of relations, 748 segments, and 35\nreset epochs with their 35 follows relations. The view lists the\nsessions the round headers name instead of reading them back out of a\nsession node\u0027s id, which now writes a \"%\" as \"%25\".\n\nA record that names the stream which started a nested child names no\ncall, but the way back from a child counted it as a second call. A child\nthat such a record named, and whose one call received its result, got no\nresult_of, and its returned_value said unavailable. The record now agrees\nwith the call when that stream made it, and one that names another\nstream, landed or not, leaves the relation out.\n\nThe round reader skipped empty lines before it computed the digest and\nbefore it checked that nothing follows the commit, so a round with a\nblank line added still verified. Session Flow says a reader rejects a\nline that does not decode and a line after the commit, and the reader\nnow does.\n\nThe asz.view page says plainly that 0.6.0 removed a segment\u0027s state and\ncommittable and kept the version 1.0, the one exception to its rule, and\nlists auxiliary among the roles of a stream, as assembly writes it."
    },
    {
      "commit": "da56fb1fecaaed02f5130f776e810b2c703f96dc",
      "tree": "70228804c7ee89c9d215b8c0ec2f0adaaefecd75",
      "parents": [
        "1a8d49d9e9248e159b67c636bb7cb2f769810545"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 21:47:48 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 21:47:48 2026 +0800"
      },
      "message": "Fix repack, OTLP export and container image issues from the release review (#81)\n\nasz repack moved the idle clock that retention reads from the header of\nthe file with the highest sequence. A file it cut kept the time of the\nfirst file its records came from, and files were numbered stream by\nstream, so the highest sequence need not be the newest file. Retention\ncould remove or retire a session in the new root that the source root\nkept. Each new file now has the header of the newest file its records came\nfrom, and files are numbered in the order their records landed.\n\nasz repack counted only record lines against the budget, so a file of\nseveral records could be larger than max_delta_bytes. It now cuts with\nsessiondata.Cut, header and closing line included, through the new\nCut.TakeRaw.\n\nasz repack carried only \u003ckind\u003e.cursor. A second source of a stream, such\nas a second recorder directory, keeps \u003ckind\u003e-\u003chash\u003e.cursor, so collection\ninto the new root read it again from its start. Every cursor is carried\nnow. Each names the new file holding the last record its own source\nlanded, and an origin relative to the storage root, a recorder inside it,\nis written again with its cursor\u0027s name as a collector into the new root\ncomputes them; the rule for both moves to storage, where the change\ncollector and repack share it. Records of two files are cut into one only\nwhen the files are next to each other and their headers are the same apart\nfrom seq and at, so every file names its own source and no record moves\npast another.\n\nA data part holding a byte that is not valid UTF-8 landed as it was, and\nthe OTLP export could never send its file, since a protocol buffer string\nmust be valid UTF-8. The writer now takes such data out: the part says\nomitted, or truncated while its text is left, a change or execution record\nis written again with U+FFFD, and the record\u0027s dropped list says why, so\nit is not taken for a payload too large to land. The export reports a file\nthat cannot be encoded on its own and keeps it out of every request, and a\nfailed request no longer leaves out of the pass the file being batched\nwhen it failed.\n\nThe OTLP HTTP client read a 2xx answer whose body was cut off as a full\nsuccess, and an answer longer than the 1 MiB it reads as its first MiB,\nand recorded every file of the request as sent. Both are errors now, and\nthe request goes again.\n\nThe container image declared /asz/data without making it, so Docker made a\nnew named or anonymous volume owned by root, and the first collection\nfailed with permission denied. The image now holds the directory, owned by\nthe user it runs as, 65532."
    },
    {
      "commit": "1a8d49d9e9248e159b67c636bb7cb2f769810545",
      "tree": "d6fc59c7f07c196e0773c9a0efdfceb7dd872b4a",
      "parents": [
        "b5a56e34fa302b161b0b6f032927501633d03675"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 20:18:44 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 20:18:44 2026 +0800"
      },
      "message": "Correct the release guide, the Collector example and the budget promise (#79)\n\nThe release guide\u0027s commands for preparing a candidate by hand did not set\nthe LangChain plugin\u0027s version in plugins/langchain/pyproject.toml, which\nprepare does. A candidate prepared by hand would have built PyPI packages\nof the previous version. The commands now do what prepare does, the next\nversion\u0027s changelog page, the push and the pull request included. In a\nclone at 0.6.0 they made the same two trees as prepare --no-push.\n\nThe guide also named the change recorder\u0027s Debian package asz-claude-code,\nits name before 0.5.0. The Makefile builds asz-changes. The vote mail in\nthe guide lacked the note on the Debian packages that candidate writes. A\ntest now holds the vote mail\u0027s list of packages, and the names of the\npackages that install a binary, to the Makefile.\n\nThe OpenTelemetry Collector configuration on the export page had no\nmetrics pipeline. asz sends metrics by default, and that Collector refused\nthem, with Unimplemented over gRPC and 404 over HTTP, so asz push failed.\nCI\u0027s Collector check kept its own copy, which had the pipeline, so it\npassed. It now reads the configuration from the page.\n\nThe changelog and several pages said every landed file fits\nmax_delta_bytes. A single record still larger with every payload taken out\nlands as it is, over the budget. The pages now say so, and a test reads\nsuch a file back.\n\ncandidate runs cmp and publish runs perl without checking for them first,\nas each does for every other tool. Both now check. publish\u0027s printed next\nsteps left out the upload to PyPI, which the guide lists; they now name\nthe pypi skill between apt and Scoop, and a test holds their order."
    },
    {
      "commit": "b5a56e34fa302b161b0b6f032927501633d03675",
      "tree": "508d8e5ee3a11a101bf68139fe9040a6aba6c2b1",
      "parents": [
        "418eb2346552f357e1da8d8155637a15d3d8702c"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 17:21:08 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 17:21:08 2026 +0800"
      },
      "message": "Draw where a model call\u0027s history changed on the page (#78)\n\nThe embedded renderer moves to Horizon d5830a1\n(apache/skywalking-horizon-ui#202). It draws the history.change card\njust before its call, and the Prompt tab\u0027s second mode, now \"What\nchanged\", says what the comparison found instead of comparing two bodies\nby position. The old mode reported every Claude Code 2.1.302 request that\ncontinues a provider thread as a rewritten history."
    },
    {
      "commit": "418eb2346552f357e1da8d8155637a15d3d8702c",
      "tree": "6476389799d428f5875065f33e750937b24f5d6a",
      "parents": [
        "72eef4cd5c3ca31ff1d0cb10ef39324f58eee046"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 15:45:01 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 15:45:01 2026 +0800"
      },
      "message": "Show where a model call\u0027s history changed (#77)\n\nEach model call whose request records its message list is compared with\nthe call before it in its stream and epoch, from the index alone. A\nrequest that continues a provider thread holds only its new messages; its\nhistory is the history of the call it continues, then that call\u0027s reply,\nthen its own messages. A call the framework made for its own purpose is\nnot compared and is skipped.\n\nA call now says what it found: history is kept, changed, first,\nnot_compared, or unavailable with the reason, with the history\u0027s length,\nthe call it was compared with, and what became of that call\u0027s reply. A\nClaude Code call also says whether the system prompt changed. A changed\nhistory draws a history.change step just before the call, under the same\nparent, naming the two requests\u0027 records, with what was kept, changed,\ndropped and added, whether the order changed, and its cause unavailable.\nIt is no context reset: the calls stay in their epoch, and a reset is\nstill only an explicit record. The card carries no content.\n\nEvery walk is bounded and measured once a call: a thread\u0027s history is\nmeasured without building it, a list over a base copies only the front it\nkeeps, and each base is checked on its own.\n\nTwo scenarios hold a trimmed history and a provider thread, and new tests\ncover the comparison rules, the round chain publishing, revising and\nremoving a card, and a chain with no history getting it in rounds over no\nnew file. Scenario comparisons now compare every history value per call\nand card. On every LangChain capture and on the Claude Code 2.1.302\ncapture, no history changes without a mark, so no card is drawn."
    },
    {
      "commit": "72eef4cd5c3ca31ff1d0cb10ef39324f58eee046",
      "tree": "69f6d1828ce4a8cec65d8c84112e5c30e0e87df8",
      "parents": [
        "f6e3274847c33384be13bb01fbc3218a97c4f6b0"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 18 15:27:01 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 18 15:27:01 2026 +0800"
      },
      "message": "Record what each model call was sent, for comparing histories (#76)\n\nA provider body\u0027s manifest now says what its call\u0027s history was, so a\nlater change can show where a call\u0027s history changed without reading a\nbody. On a request, messages lists its messages in order, one entry of\ntwo digests each, written whole or against the body its copy came from;\non a response, reply names the message it produced. A Claude Code\nrequest also records its system prompt without the billing header, and\nthe provider thread it starts or continues. history_form names the\nrules, claude-code/1 or langchain/1, and nothing a runtime recorded is\nleft out of a message. A list that would take more than a quarter of\nmax_delta_bytes is unavailable as too large, since the budget never\ntakes a manifest out. A manifest whose history keys do not read is read\nwithout them, and still joins its call.\n\nA LangChain model call\u0027s record is flagged internal_call when LangChain\nmade it for its own purpose, from lc_internal_call in its start\u0027s\nmetadata, and turn_call otherwise. Only a start decides, and an update\ncarries its start\u0027s class through the receiver\u0027s state. A LangChain\nreply is landed only beside a request read as one prompt.\n\nThe index reads all of it, and its schema moves to 17.\n\nMeasured on a fresh session of Claude Code 2.1.302: a process\u0027s first\nrequest starts a provider thread and holds the whole message list, and\nevery later request continues it and holds only the new messages. With\neach such history built from the call it names, every pair of\nconsecutive calls kept the history before it, with the reply next,\nthrough six resumes, until the compaction. A redacted copy of the\nsession is a test fixture. On the LangChain captures, with a new\nstreaming case, every one of 80 message ids sent more than once has one\nform. At the default budget the history keys are about 5% of the\nprovider files\u0027 bytes."
    },
    {
      "commit": "f6e3274847c33384be13bb01fbc3218a97c4f6b0",
      "tree": "e779041127f5d9c455776f67c92f6d6984ad70c3",
      "parents": [
        "06c5ab2b134806c2d4f2ee58184aa3a16c210a10"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Oct 17 22:49:14 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 17 22:49:14 2026 +0800"
      },
      "message": "Warn when retire_after is shorter than a runtime keeps its history (#74)\n\nA retired session is never collected again, even when its runtime\nresumes it. When storage.retention.retire_after is set, asz now says how\nit compares with how long each enabled runtime keeps a session.\n\nFor Claude Code, asz reads cleanupPeriodDays from the user\u0027s\nsettings.json and from the managed settings for the whole machine:\nmanaged-settings.json, then managed-settings.d/*.json in the order\nClaude Code sorts them. A managed value wins over the user\u0027s. A file is\nread as Claude Code reads it: a regular file only, a leading byte-order\nmark stripped, UTF-16LE after FF FE, and empty settings by JavaScript\u0027s\ntrim. Only a whole number of days of at least 1 counts. A file asz\ncannot read or parse, and an invalid value, is named, and nothing is\nassumed for it.\n\nThis is an inspected value, never the effective one. asz always says\nthat Claude Code\u0027s effective retention was not determined: other\nsettings can change it, Claude Desktop and Cowork keep sessions at any\nage by default, and settings it cannot read pause its cleanup. It warns\nin addition when retire_after is shorter than the inspected value, or\nthan the default of 30 days when none is set. With an explicit\nsource_root it inspects nothing and says so. Kiro keeps a session until\nit is deleted, so it warns whenever kiro-local is enabled. LangChain has\nno source and gets no warning.\n\nThe lines are said at startup, in asz sources, on the list page\u0027s\nstatus, and once in the first pass\u0027s log."
    },
    {
      "commit": "06c5ab2b134806c2d4f2ee58184aa3a16c210a10",
      "tree": "4877d93f8a392bba960a487b333cce56ea45716b",
      "parents": [
        "c001d1ff2fd5e3121674aad8aed9c84e3da5fe65"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Oct 17 21:22:48 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 17 21:22:48 2026 +0800"
      },
      "message": "Retire a conversation idle for retire_after (#73)\n\nstorage.retention.retire_after is the second level of retention. A\nconversation idle that long has everything of it removed, its metadata\ntoo, and is retired: it is never collected again, even when its runtime\nresumes it. It is read on the same clock as metadata_only_after, the\nheader time of the newest landed file, and is at least as long. It needs\nmetadata_only_after set, and equal to it a conversation is retired with\nno metadata-only step first. Either rule broken is a configuration error.\nOnly both settings off keeps everything.\n\nA pass retires at its end, after the send, before level 1, and gives no\nlevel 1 to an idle or a retired session. A session is retired once its\nchain covers its files, its metrics are derived when the pipeline\nderives them, and no removal of its files is in progress, asked under\nits locks. The steps keep a crash or a power loss from losing anything,\nor landing the session again:\n\n- The marker _retired/\u003csession\u003e, an empty file, is written and synced,\n  the errors checked, before anything is deleted. Every attempt that\n  deletes anything writes it again.\n- Each metrics request the receiver does not have is written again from\n  its receipt and synced, a request already there included.\n- The session\u0027s directory and its conversation\u0027s are renamed under\n  _removed/ and deleted. Both directories of each rename are synced, the\n  errors checked, before anything moved is deleted.\n- The requests the receiver has are deleted. The root, _conversations,\n  _removed and the spool are synced on every attempt before push.state\n  and metrics.state forget the session, and the provider\u0027s table marks\n  its lines retired.\n\nA session\u0027s owner stays in push.state while a request of it waits in the\nspool, so the request goes to that owner only. Every pass finishes a\nretirement left unfinished before anything lands, whatever the settings\nsay then, and asz parse and asz push do the same first. The pass line\ncounts retired\u003d, the startup says what each level does, and the page\ndrops a retired conversation from memory, also one another process\nretired."
    },
    {
      "commit": "c001d1ff2fd5e3121674aad8aed9c84e3da5fe65",
      "tree": "3c6b9284808945a6af9dc102edf6214c61c517db",
      "parents": [
        "067f519876b30b87c9ec0eccbfcf81fb375084bb"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Oct 17 17:49:11 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 17 17:49:11 2026 +0800"
      },
      "message": "Write nothing of a retired session again (#72)\n\nA retired session is one whose marker _retired/\u003csession\u003e is in the root:\nan empty file named by its id. Nothing makes one yet. The level that\nretires a session comes in its own change, with its setting and its\ndocs. This change makes every writer leave a retired session alone\nfirst, so a session retired once is never collected, derived, parsed\nor sent again, whatever is left of it.\n\nEach writer checks the marker before it creates a session or a chain\ndirectory, or takes a lock that would create one. A marker that cannot\nbe read is an error, never taken for no marker, and the writer changes\nnothing of that session in that pass.\n\n- Claude Code, the change recorder and Kiro pass over a retired source\n  session, and lost-source marking skips it.\n- Provider bodies of a retired session are marked retired in seen.state\n  and never read again. A marker that cannot be read leaves seen.state\n  as it was.\n- LangSmith: a run whose conversation is decided and retired never\n  waits. It is remembered in that conversation, so its later updates,\n  and runs inside it, are known to be retired too, also after a restart\n  and after the run above it is forgotten.\n- parse, the metrics deriver and the SD scenario build do nothing for a\n  retired id. A marker the deriver cannot read keeps the first pass\u0027s\n  look-back.\n- push sends no landed file of a retired session. It still reads each\n  round under a retired session\u0027s directory by the session the round\u0027s\n  header names, and spooled metrics requests still go.\n- repack and the scenario runner refuse a root holding a retired\n  session, and a destination where the session is retired. The runner\n  copies only into a new directory.\n- The pass log says each retired session once per process, asz sources\n  names them for every adapter, and asz verify counts them."
    },
    {
      "commit": "067f519876b30b87c9ec0eccbfcf81fb375084bb",
      "tree": "0cd8ebe028e0dfe590e9826b155c530393bd5af2",
      "parents": [
        "01539f4e3cd7ef195ed2b5c01442b35a8b9a486d"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Oct 17 09:04:17 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 17 09:04:17 2026 +0800"
      },
      "message": "Keep only the metadata of any idle conversation, sent or not (#71)\n\nRetention removed the landed files only of a conversation that was sent.\nOne collected with no endpoint, or not sent yet, kept every byte, so a\nroot that only collects grew for ever. Whether to remove now follows the\nconversation\u0027s idle time and the setting alone, never an endpoint or\nwhat was sent. A conversation never sent loses its content everywhere,\nand its skeletons say that no receiver confirmed them.\n\nstorage.retention becomes a section, so the level that removes the\nsession itself can take its place beside it later. Its first setting,\nmetadata_only_after, is what storage.retention was: how long a\nconversation may be idle before only its metadata is kept, to save\ndisk. The single value it was is refused, with an error that names\nstorage.retention as a section.\n\nMetrics are derived only while an endpoint is set, so a conversation\nthat kept only its metadata with none set reaches a deriver enabled\nlater with none of its executions counted. A skeleton keeps no\nexecution record\u0027s protocol or server, so no MCP call is derived from\nit. The first pass of a process that saves its state now says, for each\nsession, how many distinct execution observations its skeletons keep\nthat no pass counted. It reads only execution files, and a file it\ncannot read keeps its session for another pass."
    },
    {
      "commit": "01539f4e3cd7ef195ed2b5c01442b35a8b9a486d",
      "tree": "a5f640b3a305d3e37e72304992aff93c632842b8",
      "parents": [
        "eb30abaa42566c3db6969d48bf7052b5b65fd445"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Oct 17 07:37:50 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 17 07:37:50 2026 +0800"
      },
      "message": "Collect no LangChain run whose conversation cannot be told (#70)\n\nA run\u0027s conversation can depend on something the receiver no longer\nhas. An update that names no conversation of its own is completed from\nits run\u0027s start, and a run that names no thread takes the conversation\nof a run above it. The receiver remembers both for a day, and at most\n50,000 runs. When what would say the conversation was forgotten, or\nnever came, the run landed under a name saying its identity was not\nsupplied. That put one conversation\u0027s evidence in another.\n\nSuch a run now waits, as a run waits for its ancestry, and is not\ncollected once its request has waited as long as it may:\n\n- an update with no complete identity whose start was not seen;\n- a run whose own thread is not whole yet, with the rest of its key\n  still to come;\n- a run that names no thread under a run above it that is not known,\n  one that never came, was forgotten, or is known only from updates\n  that named no conversation of their own.\n\nThe receiver\u0027s memory records whether a run\u0027s start was seen, so an\nupdate no longer proves one, and a dropped run leaves no conversation\nbehind for its start to take later. Inheritance stops at the first run\nabove that does not decide. An update that carries its conversation\u0027s\nwhole key still lands with no start seen, as every captured update\nfrom the Python client does. A trace whose runs are all known and name\nno thread still lands unassigned. A run whose conversation is known\nstill lands with whatever ancestry arrived.\n\nThe memory moves to version 2, so what it held is forgotten once. Each\npass that drops an arrival says how many, as runs_not_collected."
    },
    {
      "commit": "eb30abaa42566c3db6969d48bf7052b5b65fd445",
      "tree": "b541bb6a26f1f6ee08a1dbe32b3daccb93f7969a",
      "parents": [
        "5218ea69376197aff990e95a712204dac898ab7d"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Oct 15 09:30:19 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 15 09:30:19 2026 +0800"
      },
      "message": "Move the conversation renderer to Horizon 1401336 (#69)\n\nHorizon\u0027s renderer now reads every shape asz lands for a record cut\nshort or removed (apache/skywalking-horizon-ui#198). A change record\nwhose file list a budget cut short says how many files changed, where it\nsaid none did. A removed execution record shows its time and outcome,\nwhere it said they were not reported. A provider body whose manifest has\nno segment list is read as having none, where the Prompt tab dropped it.\nThe embedded copy moves to that commit."
    },
    {
      "commit": "5218ea69376197aff990e95a712204dac898ab7d",
      "tree": "efaa036d979a02422d0354cf182098313f7c6c39",
      "parents": [
        "eec38bffe31203e6602d07f559f5f5fb91277e03"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Oct 15 07:53:00 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 15 07:53:00 2026 +0800"
      },
      "message": "Land no line a reader may refuse, and index any label and any number of parts (#68)\n\nA review of every reader against what asz writes found two ways our own\ndata could be refused.\n\n- No landed line nests deeper than 256 objects and lists. A reader may refuse\n  a deeper line, and the OAP does, dropping every later record of the file;\n  asz landed data up to Go\u0027s own limit of about 10,000 levels. A part whose\n  data nests deeper than its 253 levels of the line is taken out as a payload\n  too large to carry is, whatever its size: too_large, or truncated while its\n  text is left, with its size and SHA-256 kept. A change or execution record\n  is never taken out whole: it is written again in its own format, without\n  the field its format does not name, and says truncated. A provider body\u0027s\n  piece is taken out by its cutter the same way. The writer changes a record\n  only when it then encodes, so a record that cannot be written stays as the\n  caller gave it.\n- The index takes a label or an identifier of any length, and a record of any\n  number of parts. A string of 64 KiB or more stopped the session\u0027s index\n  from being written, so the session never parsed again, and the 65,537th\n  part of a record took the first part\u0027s position. String lengths and part\n  positions are 32-bit, and the index schema moves to 16.\n- LangSmith tests that made a request unlandable with 9,998-deep data now\n  check it lands with the field taken out. The skeleton test of the page\u0027s\n  files route checks what the route serves today."
    },
    {
      "commit": "eec38bffe31203e6602d07f559f5f5fb91277e03",
      "tree": "77132fa3b0e18d5b883c16d013734dd6fb0617f9",
      "parents": [
        "71150c3e1285c8f0527fca8ccf6e8f4e303082d1"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Wed Oct 14 13:48:39 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 14 13:48:39 2026 +0800"
      },
      "message": "Show a conversation after a removal on the page, and draw the bodies landed after it (#67)\n\nRetention removes an idle sent conversation\u0027s landed files and leaves\nskeletons. The page drew removed steps, but said nothing above them, and its\nPrompt tab could not rebuild a provider body landed after a removal: the\nrenderer rebuilds bodies in the browser from the files the page serves, and a\nremoved file was left out.\n\n- The files route serves a removed file as its skeleton, uncompressed, with\n  what the session\u0027s store kept of it under \"kept\": the body of each record\n  kept as a chain tip, by record id, and each removed piece a later body\n  cites, by SHA-256. The budget counts them by the sizes the records name,\n  for what the store has, before anything is read; a file over the budget on\n  its own is served alone. A file a removal deletes while the route reads is\n  served as its skeleton. A store that cannot be read is an error, never a\n  store that keeps nothing.\n- The page says in its header how many files retention removed, and that only\n  a receiver that accepted them still has their text. Its loader hands the\n  kept bytes to the renderer, and asks again for what an answer left out.\n- The renderer is pinned at apache/skywalking-horizon-ui#192, whose prompt\n  store holds skeletons, takes a piece by the SHA-256 a removed or too large\n  part keeps, from the record that landed first, and says when a body was\n  removed, is built from a removed one, or holds a piece too large to store.\n- A skeleton keeps an empty segment list for a body whose segments were all\n  literal, so the renderer reads it as it read the body.\n- The chain test of the files route writes the fixtures of the renderer\u0027s\n  tests.\n- The OTLP exporter over HTTP keeps its own connections. It shared the\n  process\u0027s default transport, so an httptest server closing in a parallel\n  scenario broke a push it had just started (\"CloseIdleConnections called\"),\n  and closing the exporter closed every other HTTP client\u0027s idle connections."
    },
    {
      "commit": "71150c3e1285c8f0527fca8ccf6e8f4e303082d1",
      "tree": "44d5447d46223167131682af82ea614f22ac3b77",
      "parents": [
        "89dda1764f79624e69f5d9bc6fc254081fea3b7c"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Oct 13 13:51:43 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 13 13:51:43 2026 +0800"
      },
      "message": "Cut a provider body after a removal exactly as with none, and remove sessions that hold them (#66)\n\nRetention kept back every session that holds provider bodies, because a later body is cut against\nearlier ones: it copies the front of a chain tip and cites pieces earlier records hold. Those\nsessions are now removed too, and a body landed after a removal lands as the same record it would\nwith nothing removed.\n\n- providerbody.Session.Add takes a skeleton record in its removed record\u0027s place: its identity,\n  digest, size, depth, pieces and place among the chain tips, checked as the record was. The bytes\n  of a removed tip or piece come from a Store.\n- A skeleton keeps a part\u0027s too_large state, and a body\u0027s manifest as a reader decodes it, with\n  each literal string taken out and every part, piece and copy kept. So a body is accepted or not\n  from its skeleton as it was, and indexes the same.\n- The session\u0027s store, \u003csession\u003e/provider/: tips/\u003cgeneration\u003e/ keeps every chain tip at a removal,\n  named by its record id, and pieces/ each removed piece a later body cites, named by its digest.\n  A removal keeps the tips before it writes its intent; a collector keeps a removed piece before\n  the first body that cites it lands. Each file replaces what an earlier attempt left, and its\n  directories up to the session\u0027s are synced, the errors checked. A removal reclaims earlier tips\n  and the pieces once its files are deleted, and syncs the session directory after its intent goes.\n- asz verify rebuilds every body that is not removed from the store, counts the bodies a removal in\n  progress deletes, and checks again when a removal changed the session while it read.\n- The scenario writer and its expectation reader use the store too. The chain tests collect at each\n  scenario\u0027s own budget, and the scenario runner builds its comparison root at it."
    },
    {
      "commit": "89dda1764f79624e69f5d9bc6fc254081fea3b7c",
      "tree": "c69ac36366dedf14b36f3f4f2ff8f5577c270775",
      "parents": [
        "21b52700efa9c65e39db52afe7077f5518a89404"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Oct 13 10:53:00 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 13 10:53:00 2026 +0800"
      },
      "message": "Remove an idle conversation\u0027s landed files, and keep its structure in skeletons (#65)\n\n* Take out the owner adoption for push.state files an older build wrote\n\nUpgrades from older builds are not supported, so a session such a\nbuild sent takes its owner at its next send, like any other session.\n\n* Remove an idle conversation\u0027s landed files, and keep its structure in skeletons\n\nstorage.retention, -1 and off by default, removes the landed files of a\nconversation that was sent and has had nothing new land for that long,\nsuch as 72h or 30d. asz collect and asz server remove at the end of a\npass, after the send, a conversation\u0027s files together, once its head\nround covers them and, with metrics derived, every file of it is\nderived. A conversation never sent is kept, and so is one holding\nprovider bodies, for now.\n\nEach removed file is replaced by a skeleton, \u003cname\u003e.sk: Session Data,\ncompressed with gzip, that keeps every record and part in its place\nwith every identifier the index reads, a changes record\u0027s files and\nlines without their diff, an execution record\u0027s join, time and outcome,\nand no content. Its header records the file\u0027s digest,\nsize and record count, when and by which removal it was removed, and\nwhat the receiver confirmed of it. The index rebuilt from skeletons is\nthe index rebuilt from the landed files, byte for byte, so a\nconversation that becomes active again continues the same chain. Every\nscenario run twice, once removing everything after each send, sent the\nsame files, rounds and metrics, byte for byte. On one root, 847.5 MB of\nlanded files had skeletons of 29.6 MB.\n\nA removal writes removing.json first, then each skeleton, synced with\nthe error of its first sync checked, then deletes each file whose\nskeleton reads whole and records its digest, then syncs every directory\nof the removed range before the intent goes. The next pass finishes a\nremoval a crash or a power loss stopped, before anything lands, and no\ncollector lands into a session while its removal is unfinished.\n\nEvery reader of landed files reads a skeleton. The export never sends\none, verify reports removed files apart, the page marks removed steps,\nasz show says a record was removed, LangSmith\u0027s next position goes on\nfrom one, and asz repack refuses a session with skeletons. Metrics\nturned on after a removal count the tokens a skeleton keeps."
    },
    {
      "commit": "21b52700efa9c65e39db52afe7077f5518a89404",
      "tree": "5cff4a620f83de5864916a7412d5053ee7e5b19c",
      "parents": [
        "423d8b36bd15ce13a3ce6ab520dd16ce438549ad"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Oct 13 07:56:59 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 13 07:56:59 2026 +0800"
      },
      "message": "Hold a storage root for one command at a time, and send each session to one owner (#64)\n\nTwo commands over one root each load shared state, such as push.state,\nthe metrics state and a session\u0027s index, and save it back. A new root\nlock, _root/.lock, makes them take turns. asz collect and asz server\nhold it for each pass, asz parse and asz push for their run, asz repack\nfor the root it reads and the root it writes, asz scenario build for\neach session it builds, and asz scenario check for each root it builds.\nA single pass waits up to two minutes. A watching pass that finds it\nheld does nothing that period and says so once. asz view and asz index\nonly read. The LangSmith receiver is the one writer outside a pass: it\nonly adds requests to _langsmith/, under that directory\u0027s own lock.\nasz repack refuses a destination inside the root it reads, since every\ncommand would take it for a session.\n\nA receiver holds a session whole, so a session now goes to one owner:\nthe endpoint, the configured service name and the instance it was\nfirst sent under. push.state records the owner, and saves it, before\nthe first request carrying anything of the session goes out: a landed\nfile, a round whose header names it, or a metrics request derived from\nit. A request whose answer never came still leaves the session with its\nowner, and a save that fails sends nothing. The service is the\nconfigured one, never the one a session resolves to, because that\ndepends on reading the session\u0027s files and a person cannot set it back.\n\nAfter the endpoint, the service name or the instance changes, a session\nsent before is held. None of its files, rounds or metrics are sent or\nrecorded as sent, every pass says why, and asz push exits non-zero.\nSetting the three back sends what waited. A round goes by the session\nits header names, so a held session never holds another\u0027s round. Every\nsession an earlier build sent takes the owner of the first pass after\nthe upgrade, before it sends anything, and push.state then says\n\"owners recorded\", so this happens once. Removing a scenario session\ndrops its owner line once nothing of the session is left."
    },
    {
      "commit": "423d8b36bd15ce13a3ce6ab520dd16ce438549ad",
      "tree": "39e7285941dd03010ba9ab35132969ef6ee0ef04",
      "parents": [
        "9dd07db6c947b46bf6d97a5ffccd6c3051f45d33"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Oct 12 23:15:37 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 12 23:15:37 2026 +0800"
      },
      "message": "Derive metrics only when they will be sent, and report a failed directory sync (#63)\n\nWith no export endpoint, every pass derived the metrics and kept each\nrequest in the spool until it was sent, which was never, so the spool only\ngrew. Metrics are now derived only when an endpoint is set and\nexport.otlp.metrics is on. asz push derives what the root has not derived\nyet before it sends, under the export lock, so a root collected with no\nendpoint still carries its metrics when it is pushed later, and a file it\ncannot derive fails the command.\n\nAfter the rename that publishes a landed file, a round or a state file, the\ndirectory is synced so the name survives a power loss, and a failure there\nwas ignored without a word. It is now reported once for the directory. The\nwrite still succeeds: the file is in place, which is all a process crash\nneeds, and another process may have read it. A filesystem that cannot sync\na directory at all reports nothing; on macOS a plain fsync is tried first.\n\nThe verify package\u0027s record digest field, never filled, is removed: a\nrecord\u0027s sha digests source bytes the root does not hold.\n\nTwo tests cover an upgrade cut into several rounds: one loses\nconversation.state after the first round, and in the other a round written\nby hand removes, adds, revises and leaves an entry open, and the next\nparse, one frame a round, puts the conversation back."
    },
    {
      "commit": "9dd07db6c947b46bf6d97a5ffccd6c3051f45d33",
      "tree": "1400e21039fce74fc1f853cb55081fca46449e14",
      "parents": [
        "aa67971da10c82ef5b840524c9349c6a9510fbe3"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Oct 12 20:32:51 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 12 20:32:51 2026 +0800"
      },
      "message": "Fit every landed file to its budget, and take out what one record cannot fit (#62)\n\nA landed file travels whole as one log record, so max_delta_bytes is the\nmost a receiver has to store at once. Two things broke that.\n\nA collector cut a file by the bytes it read, and a record is larger than its\nsource line by its envelope: 4 KiB of short lines landed as 19 KB. Files are\nnow cut by what their records take once written (sessiondata.Cut), in the\nClaude Code, Kiro, changes and LangSmith collectors and the scenario writer.\n\nOne record cannot be cut: a pasted screenshot measured 4.6 MB in one\ntranscript line. Such a record lands alone, and its largest payloads are\ntaken out, one at a time, until the file fits. A part loses its data before\nits text, so a result keeps its readable text and says truncated; a part\nwith nothing left says too_large. Either keeps its place, kind and\nidentifiers, the original size, and the SHA-256 of what went first, in a\nnew part field, sha256. A change record or an execution record is never\ntaken out whole, since a step joins to it: a change record shrinks in its\nown format, its hunks and then its file list going, and its document entry\nsays so with a new key, state. The provider cutter fits its own records and\nnever takes out a manifest: a later body names a piece taken out as any\npiece, and a body of short strings lands whole and is taken out whole. Such\na body cannot be rebuilt from the root; Session.BodyFrom rebuilds it from\nanother copy. A step that stands on a whole record says too_large when a part\nof it was taken out. asz verify counts both apart from damage, and holds\nevery body\u0027s record to its manifest.\n\nThe tail reader, after one line longer than the budget, read on to the end\nof the source and landed every line up to the last in one file: one 4.6 MB\nline made an 8 MB file. It now takes that one line, and stops growing its\nwindow when the source was cut short while it was read."
    },
    {
      "commit": "aa67971da10c82ef5b840524c9349c6a9510fbe3",
      "tree": "e87c66433b62e468ec9570525d6adc2c312b94a8",
      "parents": [
        "6fbf0ae6b8f2b80332989e96ed49da6d611b437e"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Oct 12 11:28:18 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 12 11:28:18 2026 +0800"
      },
      "message": "Carry only what a reader needs in a round, and keep a round inside its watermark (#61)\n\nSegments are activity windows only. A segment node carries no attributes:\nits talks are its in_segment relations, and its time range is theirs. Its\nstate, gates_unmet and committable go, with the four gates that set them. A\nround carries no clock, so the last window of every session stayed open for\never, and nothing read the state.\n\nA round no longer repeats what it says elsewhere: the session\u0027s conversation\nand title_from, an epoch\u0027s records, a talk\u0027s loops, runs and trigger, a run\u0027s\ntrigger, a call\u0027s fragments and a tool\u0027s name. The header\u0027s bash_runs is\ncounted from the tool call nodes assembly made. A provider body\u0027s manifest no\nlonger repeats a request\u0027s run or the reason a body was kept whole.\n\nA round cut at the byte budget reads nothing past its watermark. Record\nlookups reached past through_seq, so a round could hold structure its header\nand input digest did not cover. Assembly now works on the index as it stood at\nthe watermark, and the index is refused when its entries are out of landed\norder. The Claude Code provider adapter and the scenario writer extend only an\nindex that agrees with its saved state, as the other adapters do.\n\nAn existing chain continues: its next round revises the changed nodes over no\nnew file. On a long conversation that revision did not fit one round, so\nrevisions that need no new evidence now go first, in rounds of their own cut\nby their frames, before the round of the new evidence. A session whose parse\nfails is parsed again on every pass, and no scenario removal takes it until a\nparse succeeds."
    },
    {
      "commit": "6fbf0ae6b8f2b80332989e96ed49da6d611b437e",
      "tree": "b4fc205fd21e5af7764c4a7d1010cab62308f3d2",
      "parents": [
        "8104ada77cbd0d5ca69754d50cbbc0cd6f9bbec5"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Oct 11 20:21:17 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 11 20:21:17 2026 +0800"
      },
      "message": "Collect the sessions Kiro\u0027s V3 engine writes, for kiro-cli and Kiro Crew (#60)\n\nA new adapter, kiro-local, reads the sessions Kiro\u0027s V3 agent engine\nwrites under ~/.kiro: kiro-cli on its V3 engine, and Kiro Crew on its\nkas backend. It is on by default and needs nothing from Kiro. A session\nis read when its header names schema 1.0.0. kiro-cli\u0027s V2 sessions and\nV3 sessions of another schema are counted and not collected.\n\nMeasured with kiro-cli 2.28.0 and its engine @kiro/agent 0.66.26, and\nKiro Crew 0.7.2, over 86 sessions and 3,101 records, and with Kiro Crew\n0.8.0, which bundles kiro-cli 2.27.1 and engine 0.66.22, over 13\nsessions and 2,069 records.\n\nKiro ties no record to a provider call, so model output whose record\nnames no call is now a step of its run, with no llm.call around it. A\nsidecar of the main stream names the conversation, because Kiro keeps a\nsession\u0027s title in its header. control.notice joins model.Vocabulary,\nso every glossary is checked for it.\n\nA file that fails to land now leaves nothing behind in any collector.\nIts records entered the index during the write, and it kept its number,\nso a failed rename left index entries for records that never landed and\na gap that every later round reported as a missing file. Records enter\nthe index once their file has landed, and a file that did not land gives\nits number back.\n\nKiro Crew deletes a task run\u0027s step sessions within seconds on kas, so a\ncollector misses most of them; kirodotdev/KiroCrew#18988 proposes a\nsetting that keeps them for a while."
    },
    {
      "commit": "8104ada77cbd0d5ca69754d50cbbc0cd6f9bbec5",
      "tree": "7ddade0477c0fabe39fdee7e6bbe255527af757a",
      "parents": [
        "7d270e355796f31a152f75e4f983fe1d83b938fc"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Oct 10 08:22:40 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 10 08:22:40 2026 +0800"
      },
      "message": "Copy over plain HTTP, and put each Prompt tab copy button in its title (#59)\n\nA browser gives a page the Clipboard API only on HTTPS or localhost. On\na page served over plain HTTP from any other address, every copy button\nof asz view did nothing and said nothing. Horizon\u0027s renderer now copies\nthrough a selection there, and puts each copy button of the Prompt tab\nin the title of what it copies, so the whole body and the settings copy\nwhile their section is closed (apache/skywalking-horizon-ui#190).\n\ninternal/view/conversation-view is rebuilt with\ntools/conversation-view.sh update e55f329bc91c370ec15f0662f06bc5cd9ac386b0."
    },
    {
      "commit": "7d270e355796f31a152f75e4f983fe1d83b938fc",
      "tree": "32265e6641ebfe02497e07b8b006e4a1d8ee5a6c",
      "parents": [
        "7208bb358900c4024fec1b43b54c7764a8858269"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Oct 09 14:28:37 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 09 14:28:37 2026 +0800"
      },
      "message": "Remove the claude-code-otlp receiver (#58)\n\nThe receiver accepted what Claude Code\u0027s own exporter sent and kept\nnone of it. Every metric asz sends is derived from the landed files, so\nit fed nothing. A Claude Code pointed at it lost the metrics only the\nexporter has, such as cost and active time, which the SkyWalking OAP\nreads when the exporter is pointed at the OAP directly.\n\nThe adapter, its configuration entry, its end-to-end check and its\npages are removed. A configuration written for 0.5.0 still names it.\nThat file still loads. An entry with no enabled key stays off, as it\nwas, and an enabled entry is skipped with a line that says where to\npoint the exporter instead.\n\ngolangci-lint moves to v2.14.0. CI takes the newest Go 1.27 release,\nand Go 1.27.2 writes export data version 5, which v2.13.2 cannot read,\nso lint failed with a type-check error. v2.14.0 reads it."
    },
    {
      "commit": "7208bb358900c4024fec1b43b54c7764a8858269",
      "tree": "546c8589b13e840fa353e293975353a24c067321",
      "parents": [
        "8ea6f31774a81f23ed0aa5c2f07c8e8a083fce40"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Oct 09 09:16:52 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 09 09:16:52 2026 +0800"
      },
      "message": "Give a run that supplies no thread the thread of the run it ran inside (#57)\n\nLangSmith JS passes a decorated function\u0027s metadata to none of the runs\ninside it. A model call made inside a traceable function therefore named\nno thread, and landed under its trace in a conversation of its own: the\nanswer was there, and the conversation of the question had none. The\nPython client passes the metadata down itself, so its runs were whole.\n\nA run that supplies no key now takes the conversation of the nearest run\nit ran inside whose conversation was supplied, read from its dotted\norder. A run that supplies its own key keeps it, and a trace that\nsupplied none anywhere still lands unassigned. A run above that supplied\nnothing, or landed unassigned under either name an unassigned run can\nhave, is passed over. A run above that is not remembered, because it has\nnot arrived or was forgotten, stops the search, since it may have\nsupplied a thread of its own.\n\nThe order and the batching of requests do not decide it. The\nconversation is decided once the run\u0027s ancestry has arrived, which the\nreceiver already waits for. Every arrival is remembered as it is read,\nbefore its request lands or waits, so a run below it in another request\nfinds what it supplied, or that it supplied nothing. While a request\nwaits, a run that supplied no key is remembered with no conversation,\nsince the unassigned name remembered there was kept when the request\nwas tried again after its root arrived.\n\nOne run is one conversation. Its own key decides from the first of its\narrivals that brings one, in one request or across waiting requests,\nand once it has landed every later arrival follows it. A run is counted\nunassigned once, not once per arrival.\n\nAn update carries only what changed, so it can name a thread with no\nproject and arrive before the run\u0027s start. Remembered as the run\u0027s\nconversation while it waited, it named the conversation with an empty\nproject, and the start that carried the project could no longer decide,\nas it could before every arrival was remembered. Such a thread is now not\nremembered as the run\u0027s conversation; the run does not take a thread\nfrom above in place of it, and a run below does not pass over it.\n\nThe JS traceable capture now lands as one conversation and assembles\nwith the question, the model call and its answer. Twelve tests cover\nthe rule, including one that sends the same runs in several orders and\nbatchings; breaking each of fourteen parts of the rule fails one."
    },
    {
      "commit": "8ea6f31774a81f23ed0aa5c2f07c8e8a083fce40",
      "tree": "0f28bc9483285a04a00c033d9a7e313dd3dd5f0f",
      "parents": [
        "f7780089e39056afbba3adf41bb8fa765877f0eb"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Oct 08 21:41:58 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 08 21:41:58 2026 +0800"
      },
      "message": "Give the list page\u0027s test an empty browser storage (#55)\n\nThe stand-in browser handed the list page a storage whose getItem\nreturned a value where a browser with no saved theme returns null. The\npage\u0027s theme script then failed to parse it and skipped the rest of its\nblock, so a request added there went unseen: a browser would send it\nbefore the refusal, and the test still passed. The stand-in\u0027s storage is\nnow empty, as a browser\u0027s is before a theme is saved, and that edit\nfails the test."
    },
    {
      "commit": "f7780089e39056afbba3adf41bb8fa765877f0eb",
      "tree": "bd12abdf06fab00564e2fcc4b7cab2ff84c2c384",
      "parents": [
        "b5c2c30c144270da0f2c4e28be57d513bdd48bb2"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Oct 08 20:13:54 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 08 20:13:54 2026 +0800"
      },
      "message": "Receive LangChain JS and LangGraph JS as the Python client\u0027s (#56)\n\nA LangChain JS application points its client, LangSmith JS, at the\nreceiver with the same four variables, and nothing it sent landed. Run\nagainst a real LangGraph JS agent and a real traced function, LangSmith\nJS 0.10.8 differs from the Python client in what it writes, and each\ndifference broke something:\n\n- end_time is the milliseconds since the epoch, where the receiver read\n  text, so every run was refused whole. A run\u0027s time is now read as\n  either, and a number is kept as the same RFC 3339 text.\n- A message is serialized as a constructor whose kind is named only by\n  the class that ends its id, its fields under kwargs with no type. The\n  person\u0027s question was never found in a graph\u0027s first input, so nothing\n  opened a LangGraph JS conversation; a tool\u0027s result landed as the\n  serialized message; such messages were not seen as a repeat of the\n  model call\u0027s; and a system message among a decorated function\u0027s\n  arguments landed unnamed. One reader now takes a serialized message\u0027s\n  fields and its kind, from the type Python writes or the class JS\n  names, and each of these uses it.\n- A decorated function carries no ls_method. Both clients write which\n  library made the run, langsmith for a decorated function, so that is\n  read too; read by ls_method alone, a JS function\u0027s arguments and result\n  were dropped as a graph\u0027s.\n- A decorated function\u0027s metadata is passed to none of the runs inside\n  it, so a model call it makes supplies no thread and lands under its\n  trace, as any run that supplied none does. A LangGraph JS graph puts\n  the thread on every run.\n\nThe two applications are under tests/apps/langchain-js, with their\nversions pinned, and their captures sit in the LangChain corpus beside\nthe Python ones, read by the same tests and two of their own. The\nrecorder reads the chunked body LangSmith JS sends, which it kept empty,\nand MEASUREMENTS.md counts both cases. Reverting any one of the fixes\nfails a test."
    },
    {
      "commit": "b5c2c30c144270da0f2c4e28be57d513bdd48bb2",
      "tree": "48abb575fffa89b7c53b4e9be9136ad58800e2f4",
      "parents": [
        "d19812de2ed0ce778654573cbbb81bb6103baeef"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Oct 08 09:59:50 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 08 09:59:50 2026 +0800"
      },
      "message": "Let view.hide decide, and take a request\u0027s hide only when view.hide_override is on (#54)\n\nview.hide is now the whole answer by default. A request that carries the\nhide parameter is refused with status 400, since serving it anything else\nwould not be what it asked for. Before this, the parameter was always taken\nand added names to view.hide.\n\nview.hide_override, off by default, lets a request\u0027s hide replace view.hide\nfor that request. A host that serves the API through its own route then\ndecides what each reader is kept from. A request that sets no hide is served\nwhat view.hide says, and hide\u003d with no name withholds nothing.\n\nasz view and asz server say at start-up when the override is on. The\nconfiguration, asz.view and command-line pages and the 0.6.0 changes say the\nsame."
    },
    {
      "commit": "d19812de2ed0ce778654573cbbb81bb6103baeef",
      "tree": "c3e23bc9d04ce808f011dbbeac02a43813dbdc83",
      "parents": [
        "6691c36fc9933785ba034c7241683b88c2683c38"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Oct 08 09:51:45 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 08 09:51:45 2026 +0800"
      },
      "message": "Check that the list page refuses an address that does not parse (#53)\n\nThe page test checked that the list page notices a \u0027;\u0027 in its address,\nnot that it stops: with the refusal removed and the detection kept, the\ntest still passed, and the list would offer every conversation without\nthe hide the address meant to carry. A check of the refusal\u0027s text is\nnot enough either, since it passes with the line commented out.\n\nThe test now runs the list page\u0027s scripts under node, in order, with a\nstand-in for the browser. With \u0027;\u0027 in the address the page must stop\nwith the refusal shown and ask the server for nothing. With an address\nthat parses it must reach the list. The stand-in keeps what a browser\nkeeps after a script throws: the next script still runs, a list has an\nelement in it, every timer, listener, handler and callback the page\nhands over is called once the scripts end, with a promise\u0027s callbacks\nrun between, window is the page\u0027s own global, and the body\u0027s first\nchild is the element its markup starts with. It is not a browser: a DOM\nmethod the page does not use fails inside a callback and is passed\nover. Without node the test is skipped, as the LangChain shim\u0027s tests\nare without python3."
    },
    {
      "commit": "6691c36fc9933785ba034c7241683b88c2683c38",
      "tree": "159febbe4abadf082f8866df6e3a075d49317410",
      "parents": [
        "e912e371ce2476e4fd9c67afcea3c84c3c10169b"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Wed Oct 07 23:48:17 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 07 23:48:17 2026 +0800"
      },
      "message": "Fix what a review of the withholding found (#52)\n\nA review of #51 against the code found two records that could carry a\nsystem prompt unnamed, one wrong size, one hole in the page, a test that\nproved less than it said, and three rules the asz.view page stated\nloosely.\n\n- An unfinished model call whose client put the prompt among its\n  parameters landed it unnamed. The extra lands whole with the envelope,\n  and only its tools were found by shape. A prompt under a key a provider\n  takes it by now names the record system_prompt, as the tools already\n  named it tool_schemas.\n- A graph\u0027s first input that lifts the person\u0027s message named nothing by\n  shape, though the LangChain page says a graph\u0027s first input is named\n  by the shapes it holds. A message\u0027s content can be an object of any\n  shape, and one that takes a request\u0027s shape is named now. The history\n  that is not landed is still not read.\n- A withheld step that names one empty part took the size of all the\n  record\u0027s parts. The page reserves that for a step that names no single\n  part, so a zero stays a zero. The OAP mirrors this rule.\n- The page dropped a hide written after a `;` in its address, which the\n  query parser reads as part of another value, and asked for the whole\n  conversation. The API refuses such a query, and now the page does\n  before asking.\n- The test of a root function\u0027s first input collected the flags of every\n  record, so the function\u0027s own record stood in for the first input\u0027s.\n  It checks that record\u0027s flags now.\n- The asz.view page says that hide ignores spaces around a name and\n  empty entries and counts a repeated name once; that a run journal\u0027s\n  record is what names no stream, as the code checks, and that a\n  stream\u0027s own label is not read from a flagged record; and that the\n  withheld count is as of the view\u0027s last load, which is each new round."
    },
    {
      "commit": "e912e371ce2476e4fd9c67afcea3c84c3c10169b",
      "tree": "0ab70fdebe258899c293f413472768f9ac45b692",
      "parents": [
        "adda2f78f3e71fb24b0e34435bc28aeaf8a75f72"
      ],
      "author": {
        "name": "Arnau Senserrich",
        "email": "72820586+ArnauSB@users.noreply.github.com",
        "time": "Wed Oct 07 14:33:15 2026 +0200"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 07 20:33:15 2026 +0800"
      },
      "message": "Name what the runtime sent the model, and let a view withhold it (#51)\n\nAn operator who shows a conversation to the people an agent served may\nneed to keep the system prompt and the tool schemas from them, while\nstill seeing them. asz has no notion of who is reading. So the adapter\nnames the records that carry them, and the view withholds by that name,\nnever by the text or the size of a part.\n\n- Claude Code: a prompt_snapshot is named system_prompt, and also\n  tool_schemas when it holds tools. A deferred_tools_record that holds\n  tools is named tool_schemas. The name comes from the record\u0027s type\n  and keys.\n- LangChain: the records that can hold a model request are named by\n  whether they hold one: the envelope of a call that arrives unfinished,\n  and the first input of a trace whose root is a model call. A\n  decorated function\u0027s own values are named by their shape.\n- The names go into the existing flags field of Session Data. The\n  index, the assembler and the round chain are unchanged.\n- view.hide lists the names withheld from every reader of asz view and\n  asz server. A hide parameter on the document, record and files\n  endpoints adds names for one request and never removes one.\n- A withheld step keeps its node, its flags and its size, loses its\n  text, and says omitted. summary.withheld counts every landed record\n  that carries a withheld name, once per id. A reader who withholds\n  anything is served no provider body, since a request carries both\n  again.\n- The files endpoint serves provider body files only. It served any\n  landed file whole, transcripts included.\n- The view section of the configuration is read strictly, and a file\n  with a second YAML document is refused.\n- Records landed before this change carry no names. Collecting the\n  sources again into a new root names them.\n- The LangChain demo uses a neutral example, and its captures were\n  recorded again. The docs point to the SkyWalking issue tracker.\n\nOn 66 local Claude Code conversations, 834 records are named\nsystem_prompt and 632 tool_schemas, and the view withholds and counts\nevery one. On LangChain captures of each named path, every named record\nis withheld and counted. A reader who hides nothing is served the same\ndocument as before, apart from an empty summary.withheld."
    },
    {
      "commit": "adda2f78f3e71fb24b0e34435bc28aeaf8a75f72",
      "tree": "83232405b7fb07cdde778e5bd475a4f3e5ea6a43",
      "parents": [
        "6dbd2ba66014d9652668dcd7f7d768a31456aab8"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 28 12:04:41 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 28 12:04:41 2026 +0800"
      },
      "message": "Say how deep a line may nest (#50)"
    },
    {
      "commit": "6dbd2ba66014d9652668dcd7f7d768a31456aab8",
      "tree": "467055d8ef9a9099b8d828e24a213bdb861eeb75",
      "parents": [
        "aeff5f75e6995a1556d499ed74ac36dd9ad95bd0"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 28 10:20:15 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 28 10:20:15 2026 +0800"
      },
      "message": "Fix what the review of the MCP and format work found (#49)\n\n- An MCP tool name splits into a server and a tool only when exactly one\n  \"__\" follows \"mcp__\", counting the ones that overlap. mcp__foo___bar\n  is foo and _bar, or foo_ and bar, so it is left whole. It was split as\n  the first.\n- On one record, a node on the whole record comes before the nodes on its\n  parts, which follow their block. The two were decided by id, which made\n  a cycle, so a sort could return any order. The OAP already orders them\n  this way.\n- The page reads record times by decoding each record, so they end where\n  Session Data says a reader stops: at a line that does not decode. On a\n  142 MB session this takes about 0.25 s more. A file\u0027s and a node\u0027s first\n  and last time keep a time before 1970, and no longer depend on map order\n  when a record is at exactly 1970-01-01T00:00:00Z, which counts as none.\n- asz.view no longer says the document carries no RFC 3339 strings: a\n  workspace change and a tool execution keep their record\u0027s time as one.\n  It says how siblings on one record are ordered. Session Data and the\n  OpenTelemetry export list the execution kind.\n\nThe documents of the 94 real conversations measured are unchanged."
    },
    {
      "commit": "aeff5f75e6995a1556d499ed74ac36dd9ad95bd0",
      "tree": "544c2b103ec1640430387bf7518431b037adfa9d",
      "parents": [
        "fc04fa1248ddf495736106f17fd70057212bc97e"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 28 09:32:04 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 28 09:32:04 2026 +0800"
      },
      "message": "Draw calls to MCP servers, and what each Evidence position carries (#48)\n\nThe page\u0027s renderer is Horizon\u0027s, and two Horizon changes since the pin\ndraw what asz.view already carries. apache/skywalking-horizon-ui#175 puts\na call to an MCP server on its own lane, with how it ended, the measured\ntime and an Execution tab. #176 makes the Evidence tab show what the\ndocument carries for the picked position: the step\u0027s text, a call\u0027s\nresult, or a change or execution record. It showed the step\u0027s text under\nevery position.\n\ninternal/view/conversation-view is rebuilt with\ntools/conversation-view.sh update c903e77876c99358873b89f4803becdc5470ca30."
    },
    {
      "commit": "fc04fa1248ddf495736106f17fd70057212bc97e",
      "tree": "c39985eff2ff06697ad3ac235f4eaf4ae73f97e0",
      "parents": [
        "130601cb857ded78989bd883bc7fc2ff1367d8a0"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sun Sep 27 09:14:42 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Sep 27 09:14:42 2026 +0800"
      },
      "message": "State the rules a reader follows on the format pages (#47)\n\n* State the rules a reader follows on the format pages\n\nThe formats are JSON lines and JSON, and a reader in another language,\nsuch as the SkyWalking OAP, builds the same asz.view document from the\nsame files. Where a page did not state a rule, such a reader had to find\nit in asz\u0027s Go code, and ended up copying what Go\u0027s libraries do. The\npages now state these rules, so a reader follows the pages.\n\n- Session Data: the type of every record, part and dropped field, and of\n  the usage values; a null is a field left out; a reader ignores a field\n  the page does not list; asz\u0027s reader stops at a line it cannot decode.\n  Times are RFC 3339 with a four-digit year, compared as instants.\n- Session Flow: attrs keys and ids sort in code point order, which the\n  page defines, and the provider_bodies rule asz\u0027s reader already applies.\n- asz.view: times round down to the millisecond; header times are null\n  or 0; attrs and data are as written; a data part\u0027s text; white space is\n  Unicode\u0027s White_Space property; which records give a call its result;\n  how request_to_result_ms is cut.\n\nDocumentation only. The formats stay sd/1, sf/1 and asz.view 1.0.\n\n* Say how a view writes a call\u0027s usage and a step\u0027s dropped list\n\nThe counts in usage leave out a count of zero, a dropped entry holds its\nwhat and bytes, and why when there is one, and a field the Session Data\npage does not list is not copied. A reader in another language needs this\nto write the same document.\n\n* Say how deep a view writes a tree\n\nA view writes each tree down to twelve levels below its root, a talk or an\nentry of loose, and leaves a deeper node out. The page said the document\nholds every run and step, which is true only within that depth. No talk\nmeasured has more than three levels."
    },
    {
      "commit": "130601cb857ded78989bd883bc7fc2ff1367d8a0",
      "tree": "38196ee8dde68201873bdaed4daaeb9f5cceefc9",
      "parents": [
        "3fdaa2cac4b82fc30d826a6e1d8cfda0914b57fb"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 26 17:54:06 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Sep 26 17:54:06 2026 +0800"
      },
      "message": "List what happened in the order it happened, never by id (#46)\n\nA stream\u0027s origins, the relations list and a node\u0027s edges were ordered\nby relation id. The ids end in hashes, so a workflow launch listed the\nstreams it started in hash order. On 94 real conversations, 130 of the\n150 steps that started several streams led with a stream other than\nthe one their run named first.\n\nThey are now ordered by where things happened. Inside one stream or one\nworkflow run, a record\u0027s position decides: seq, row, block. Across\nstreams, time decides, since a child\u0027s file can land before its\nparent\u0027s. The two are combined as a merge: each lane keeps its position\norder, and the lanes are merged by time. A single comparison that\ndecides some pairs by position and others by time is not transitive.\n\nA relation is placed at the earliest record its evidence names, by the\nsame rule. Each stream\u0027s origins are ordered on their own, and an\norigin that is not listed takes no part in the order.\n\nWorkspace changes and tool executions stay in time order. Records of one\ninstant are now ordered by where each was read, not by id and not by\nhow the time is written.\n\nAn id decides only between items one record supports. On the 94\nconversations the content is unchanged; the order changed in 57."
    },
    {
      "commit": "3fdaa2cac4b82fc30d826a6e1d8cfda0914b57fb",
      "tree": "a6125b45ae63a46ac4ef561ae68c7f4823a6b54b",
      "parents": [
        "80f64187e9c8f5129ed0efb1804ccc928d5d5aa5"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 26 11:10:19 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Sep 26 11:10:19 2026 +0800"
      },
      "message": "Say that Claude Code\u0027s tool timing export is unmeasured (#43)"
    },
    {
      "commit": "80f64187e9c8f5129ed0efb1804ccc928d5d5aa5",
      "tree": "4d391e4898098d8e42a05f930770c1b553c2bf61",
      "parents": [
        "03c5a8f3d4ac005d2cfcf17c443e925f933b70bf"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 26 10:55:35 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Sep 26 10:55:35 2026 +0800"
      },
      "message": "Name LangChain files for their header\u0027s time, and keep a stream\u0027s origins in one order (#45)"
    },
    {
      "commit": "03c5a8f3d4ac005d2cfcf17c443e925f933b70bf",
      "tree": "eca4d4050c219bfb2621c41e3b2a3724fee0f204",
      "parents": [
        "e88c4214dc83bf7a0819f42e214045e900adad70"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 26 08:49:08 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Sep 26 08:49:08 2026 +0800"
      },
      "message": "Record calls to MCP servers, and derive one metric family for every agent (#44)\n\n* Record calls to MCP servers, and derive one metric family for every agent\n\nA transcript names an MCP call only as mcp__\u003cserver\u003e__\u003ctool\u003e. It does not\nsay which server ran the call, how it ended or how long it took.\n\n- The claude-code adapter lands the server and the tool on a call part\n  when the name splits exactly, and the tool step\u0027s attrs carry\n  mcp_server and mcp_tool. A name with a second \"__\" is left whole.\n- The Claude Code plugin\u0027s hooks after every mcp__ call write an\n  execution/1 record, measured on Claude Code 2.1.282: the server and its\n  configuration source, the outcome, the duration, and the size and\n  SHA-256 of the arguments and the answer. The changes adapter lands them\n  as a new kind, execution. Assembly leaves them out. asz.view lists them\n  as tool_executions, and each step names its records in executions.\n- Metrics are one top-level section for the root: metrics.enabled, on by\n  default, and metrics.lookback, 72h by default. The adapter keys metrics\n  and metrics_lookback are gone.\n- claude_code.token.usage is now agent.token.usage. agent.mcp.calls and\n  agent.mcp.duration are derived from the execution records, by server,\n  tool, configuration source, outcome and query source.\n- The claude-code-otlp receiver keeps nothing. It accepts metrics, logs\n  and traces and answers in the request\u0027s encoding. Every metric asz sends\n  is derived from the landed files, so the spool\u0027s received-request path\n  and spool.state are removed.\n- The fold properties now compare each stream\u0027s record count, so evidence\n  that entered a stream would fail them. Record times in the view sort by\n  instant, not as text.\n\n* Say in the comments that the receiver keeps nothing\n\nThe metrics package, the pusher, the removal of scenario sessions and the\nlisten setting still described the receiver landing and renaming the\nexporter\u0027s metrics, and a spool holding two streams of one metric. Every\nmetric now comes from the landed files alone."
    },
    {
      "commit": "e88c4214dc83bf7a0819f42e214045e900adad70",
      "tree": "3149f9927ca594eb74fccbf9fe95e5518f04989c",
      "parents": [
        "9511667ac4d2608ba0409e7c3acbe206de1b50a2"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 25 20:16:08 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 25 20:16:08 2026 +0800"
      },
      "message": "Move the conversation renderer to Horizon 88e0110 (#42)\n\nThe Prompt tab\u0027s What it added mode said the request before was not among\nthe loaded bodies whenever it had nothing to compare with. For every\nLangChain call, and for the first call of any chain, the request names no\nrequest before it, so nothing was missing. Horizon\u0027s renderer now says the\nrequest names none before it (apache/skywalking-horizon-ui#173), and the\nembedded copy moves to that commit."
    },
    {
      "commit": "9511667ac4d2608ba0409e7c3acbe206de1b50a2",
      "tree": "24c3b23927ee8896ed96bb23a2285ce316428722",
      "parents": [
        "516287c573e90412c9c33b19ce8aaaeea5a1a390"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 25 07:35:34 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 25 07:35:34 2026 +0800"
      },
      "message": "Say that source_root is Claude Code\u0027s projects directory (#41)"
    },
    {
      "commit": "516287c573e90412c9c33b19ce8aaaeea5a1a390",
      "tree": "e54619b97ae52ea52680faec17690ad3920a45f5",
      "parents": [
        "b0317167644247141b20029c64b7983aa48f2f3a"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 25 00:33:46 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 25 00:33:46 2026 +0800"
      },
      "message": "Show a headless run\u0027s question beside its answer (#40)"
    },
    {
      "commit": "b0317167644247141b20029c64b7983aa48f2f3a",
      "tree": "ec2e9c1db22b07d5060d700ed732706034ced164",
      "parents": [
        "00020e13d01cb99e190737b0c0863eb2a8db2208"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 24 22:38:43 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 24 22:38:43 2026 +0800"
      },
      "message": "Show what a LangChain model call was sent, in LangChain\u0027s words (#39)\n\nThe page\u0027s Prompt tab read a LangChain request as Claude Code\u0027s shape and\ndrew it as one empty message. Horizon\u0027s renderer now reads LangChain\u0027s\nserialized messages: each by its own type, system, human, ai or tool, with\nthe tool calls an ai message made and each tool result under the call it\nanswers, and a response with its answer, model and token counts.\n\nThe embedded renderer moves to Horizon 297f020, which carries that fix\n(apache/skywalking-horizon-ui#172). The theme it brings also draws a light\ntheme\u0027s scrollbars and other native controls light, and lets the top bar\u0027s\nactions give way on a narrow window."
    },
    {
      "commit": "00020e13d01cb99e190737b0c0863eb2a8db2208",
      "tree": "192f401d134cee6c25b4497bbfeac4f16ec41cca",
      "parents": [
        "d912823719c1b4470dddf5f15e6575bab962560a"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 24 20:40:08 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 24 20:40:08 2026 +0800"
      },
      "message": "Remove the upgrade guides, and fix what 0.5.0 left stale in the docs (#38)\n\nThe setup pages give how to install and use asz, not how to move from\none version to another. The Upgrading from 0.4.0 section, the notes\nabout the names 0.5.0 replaced, and the history of the old adapter name\nare removed. The changelog of 0.5.0 still lists every rename. The Claude\nCode plugin keeps its upgrade steps: their order keeps the plugin\u0027s\ndata, and tools/test/claude-code-check runs them from the page.\n\nLangChain and LangGraph still said there is no context reset. A summary\nthat SummarizationMiddleware marked resets it now.\n\nThe docs welcome page said Setup starts by building the binary. Quick\nStart starts by installing it.\n\nThe LangChain plugin\u0027s README is its page on PyPI. It now says the\nplugin needs asz-changes and a tools.scope, without which nothing is\nrecorded, and links the documentation of the released version rather\nthan the version in development."
    },
    {
      "commit": "d912823719c1b4470dddf5f15e6575bab962560a",
      "tree": "07466c7e3572b89065bc9d26416d8a05cd86eef4",
      "parents": [
        "38fff28b7263c0e440b8a32c4c2112a64bfa2062"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 24 20:30:10 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 24 20:30:10 2026 +0800"
      },
      "message": "Release skills: what 0.5.0 taught (#37)\n\n* Release skills: what 0.5.0 taught\n\nThe pypi skill now says what to set up before an upload. The upload\nthat creates a project needs a token scoped to the whole account,\nbecause a project token can only be made once the project exists.\nLater uploads use a project token. The token is read from\nSW_PYPI_TOKEN, which must be in the environment Claude Code starts\nwith. The skill downloads from downloads.apache.org first, because\narchive.apache.org still had no 0.5.0 fifteen minutes after the move.\nIt verifies with gpgv, which leaves the user\u0027s keyring alone. It then\nchecks that PyPI holds the files it built.\n\nThe homebrew skill ran git rm on a path inside the temporary tap,\noutside the repository, where git stops with an error. Its template\nfits 0.5.0 and later only, so it no longer claims to fit 0.4.0.\n\nThe apt skill tests every older version in the index, because adding a\nversion moves the one that was newest to the archive.\n\n* Remove old versions without a Scoop bucket, and name what the downloads page links\n\nThe release guide and publish required a Scoop bucket to name the new\nversion before --remove-old, and the project has no bucket. That\ncondition now applies only where a bucket exists. The guide also asks\nthat archive.apache.org holds each version before it is removed, and\nthat the website shows the change, not only that it merged.\n\nThe text publish writes for the GitHub release said the downloads page\nlinks each package. The page lists the source package and the binary\narchives, not the Debian packages.\n\nThe pypi skill states only what was observed about the session and the\narchive. The note that a rebuild gives other bytes now carries its\nmeasurement: the same files with the same bytes, and different times\nrecorded for the files the build writes."
    },
    {
      "commit": "38fff28b7263c0e440b8a32c4c2112a64bfa2062",
      "tree": "46dfef61eda201e3ac14b0fc9df8db4a2967fff6",
      "parents": [
        "a5da792776ed032b739336fe0e77e339d8a20fd4"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 24 15:59:30 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 24 15:59:30 2026 +0800"
      },
      "message": "Homebrew formulae for 0.5.0 (#36)"
    },
    {
      "commit": "a5da792776ed032b739336fe0e77e339d8a20fd4",
      "tree": "37118cbd7f5588fc7b0e4f86f9fa8fd52f2de6e5",
      "parents": [
        "174a8431b8f9dd747152eeb50868d028509b381e"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 24 14:49:52 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 24 14:49:52 2026 +0800"
      },
      "message": "Take a LangChain context reset from the summary the framework marked (#35)\n\n* Take a LangChain context reset from the summary the framework marked\n\nSummarizationMiddleware marks the summary message it writes with\nadditional_kwargs.lc_source set to \"summarization\". A model call sent that\nmessage now starts a new epoch in its stream, with an epoch.boundary and\nthe message as its epoch.summary, joined by summarizes, the same way a\nClaude Code compaction does. The summariser\u0027s own call read the old\nhistory, so it stays in the old epoch.\n\nNothing is inferred from how a message list changed. trim_messages,\nlangmem and RemoveMessage write no mark and make no reset, and a marked\nmessage with no id makes none, because it cannot be told from another\nsummary with the same words.\n\nThe reset records take their ids from the summary message and the stream,\nso the summary sent again with every later call is kept once, including\nafter a lost shape or a crash that landed only the boundary.\n\nTwo fixes the work found:\n- A stream whose first record is a reset got an empty epoch with the\n  reset\u0027s own id, which followed itself. The reset-first scenario\n  reproduces it.\n- A request is placed twice, to check and then to land, and placement\n  wrote its changes back into the request. A link to a plain call landed\n  with its auxiliary flag twice. Placement now works on copies.\n\nThe summarized fixture is a real SummarizationMiddleware run against the\nstand-in model; langchain 1.4.2 is added to the pinned harness and moves\nno other pin.\n\n* Make the partial-landing test rebuild the index and read the summary back\n\nThe crash test kept the index from the first landing. Its entries still\npointed at the files the crash removed, so the test passed on counts even\nwhen the replay restored nothing. It now removes the index and its state,\nas a crash before them leaves the session, and checks that each summary\nrecord exists again with the summariser\u0027s words."
    },
    {
      "commit": "174a8431b8f9dd747152eeb50868d028509b381e",
      "tree": "84631c01c4e82ed01b77da4576c06d4e03311c9f",
      "parents": [
        "4280bb43af0296f698c4d122e58ea1c1889a49a1"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 24 08:02:08 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 24 08:02:08 2026 +0800"
      },
      "message": "Add a benchmark for cutting LangChain request bodies (#33)\n\nBenchmarkLangChainRequests cuts every request of a generated LangChain\nconversation with pkg/providerbody, the code the receiver runs, for context\nwindows of 8K to 1M tokens, with the history left to fill the window, trimmed,\nor summarised. It reports time, throughput, allocations, the landed share of\nthe wire bytes, and the number of calls.\n\nmake test and CI compile it but never run it. Its comment gives the command\nthat runs it, the reference numbers, and the machine they were measured on."
    },
    {
      "commit": "4280bb43af0296f698c4d122e58ea1c1889a49a1",
      "tree": "f6e83a4201eeecaddb9982005d0dafbbbdb6777b",
      "parents": [
        "9986b10d12278b183e471d5630281fe9fd15dda4"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 24 07:53:32 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 24 07:53:32 2026 +0800"
      },
      "message": "Require the CI check and an up-to-date branch to merge into main (#34)\n\nmain had no protection. The CI workflow already fans every job into one\ncheck, Required, but nothing enforced it. .asf.yaml now requires that check\nto pass on a branch that is up to date with main, and shows the Update\nbranch button on a pull request that is behind. No review is required: the\nproject has one committer."
    },
    {
      "commit": "9986b10d12278b183e471d5630281fe9fd15dda4",
      "tree": "0d253da263c842e93530574369f108481a71d57b",
      "parents": [
        "0e551b1ac42efbcfd3f37160c1812bef8d9a0d17"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Wed Sep 23 11:03:14 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Sep 23 11:03:14 2026 +0800"
      },
      "message": "A stream a call started leads back to it (#32)\n\nA sub-agent does a task for its caller and hands the result back. The\nmodel recorded the way in - a call starts a stream - and nothing for the\nway out, so every child read as a dead end. Claude Code\u0027s background\nchildren come back through a notification, reports, but a synchronous\nreturn had nothing, and a LangChain nested agent, which only ever\nreturns synchronously, never came back at all.\n\nA stream\u0027s final step - a child\u0027s agent.output, or an auxiliary stream\u0027s\nlast model call - now has a result_of relation to the call that started\nthe stream. It reuses the relation the model already defines, \"a record\n-\u003e the tool use whose result it carried\", because that is what the\nchild\u0027s output is to the call. It is cross-stream flow with a node at\neach end, so it is a relation, where a tool\u0027s own result stays joined by\nthe tool node\u0027s references.\n\nIt is written only when the shape makes it exact: one call started the\nstream directly, that call\u0027s result was joined and is its own, and the\nresult is not the acknowledgement of a background launch. A background\nchild keeps its way back through reports, a batch launched by a result\nthat names it is not taken for a return, and a child that never came\nback has no result_of - so a dead end now means one. The evidence is the\nresult the call received, and the child\u0027s returned_value is observed\nwhere it said unavailable.\n\nMeasured on the LangChain subagent capture, both started streams now\nlead back to their calls, exact_unique, and the test fails for both with\nthe return removed. The delegation and assembly scenarios pin result_of\nat 0 for a background launch, and fail when an acknowledgement is taken\nfor a return. Nothing is re-landed: the next round of an existing\nconversation publishes the new relation as part of its delta."
    },
    {
      "commit": "0e551b1ac42efbcfd3f37160c1812bef8d9a0d17",
      "tree": "da5e28a3fd7e0dc6d209e937d13f03e57dfd33fe",
      "parents": [
        "aa9d1b9c59f3e435c1b208b7c7b54cba28fae7e7"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Sep 22 08:13:44 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Sep 22 08:13:44 2026 +0800"
      },
      "message": "Prepare the 0.5.0 candidate and open 0.6.0 (#31)\n\n* Prepare the 0.5.0 candidate\n\nThe changelog of 0.5.0 loses its in-development note and keeps its\npath, docs/en/changes/changes.md, which the menu and the welcome page\nlink, and the Python plugin\u0027s version is 0.5.0. The tag goes on this\ncommit, as the candidate for the vote.\n\n* Open 0.6.0\n\nThe changelog of 0.5.0 moves from changes.md to its own page,\nchanges-0.5.0.md, and the version is listed under Changelog, right\nafter Current Version. changes.md is the changelog of 0.6.0 from here.\nCurrent Version and the welcome page still link changes.md."
    },
    {
      "commit": "aa9d1b9c59f3e435c1b208b7c7b54cba28fae7e7",
      "tree": "54c48feeec37f3dffe6bcd054aff393552e3fc96",
      "parents": [
        "8169164f82b9ec70da46afa2aa5e25e66fab370e"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 21 22:58:35 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 21 22:58:35 2026 +0800"
      },
      "message": "Make 0.5.0 ready to cut: one collector for every recorder, and every (#30)\n\npromise the docs make kept\n\nTwo audits of the repository, one by hand and one by GPT-6, found what\nstood between main and a 0.5.0 candidate. None of it was a rename: the\nnames 0.5.0 introduced are neutral where a thing is shared across\nruntimes and specific only where it belongs to one, and adding an agent\nbuilt on the Claude Agent SDK or on Codex adds names without changing\nthese. What was wrong sat exactly where a machine runs more than one\nruntime, and in a release step that did not exist.\n\nThe changes adapter may be named once per recorder directory, so a\nmachine that runs Claude Code\u0027s plugin and a LangChain shim collects\nboth. A second entry used to be refused, and the old name beside the\nnew one passed the check and silently replaced it, so a configuration\nkept from 0.4.0 with the documented LangChain directory added stopped\ncollecting Claude Code\u0027s changes. Two entries that read one directory\nare refused, and the old name counts as the same adapter.\n\nclaude_code.token.usage is derived from Claude Code\u0027s transcripts\nalone. The derivation read every transcript in the root, and a\nLangChain conversation carries calls and usage too, so with metrics on\nits tokens went out as Claude Code\u0027s.\n\nA session sent before its transcript landed - the recorder\u0027s files\noften come first, and their adapter is shared by every runtime - was\nattributed to Claude Code. It is attributed by the name asz gave it,\nwhich says which receiver landed it.\n\nThe Claude Code plugin installer finds the plugin under its old name\nand uninstalls it with the data kept before it removes the marketplace,\nwhich deletes a plugin\u0027s data. It copies the old data beside the new\ndirectory and renames it into place, so a copy that stops half way is\ndone again rather than skipped, and it finds Claude Code\u0027s directory\nthe way asz does. Upgrading from 0.4.0 is one ordered list on the\ninstall page.\n\nThe LangChain plugin promised pip install and nothing published it. It\nis published to PyPI after the vote, as the Homebrew tap and the apt\nrepository are written after it: the release guide has the step, the\npypi skill does it, the wheel carries LICENSE and NOTICE, and prepare\nwrites the version into the plugin.\n\nA cursor names the recorder root its file is under. Two roots read by\none configuration can hold one session under one relative path - a\nshim\u0027s flat directory and another - and one cursor for both read the\nsecond against the first\u0027s position, a truncation conflict that\nstopped both for good. The root is written relative to the storage root\nwhen it lies inside it, so a scenario root copied whole keeps its\ncursors, and absolute otherwise. A cursor written before 0.5.0 names no\nroot: it belongs to the root whose file it was read from - the file\nwith its device and inode, and the bytes before its offset, since two\nfiles can share their last megabyte and differ before it - and that\nroot writes\nits name into it on the next pass, even one that lands nothing. One\nwhose file was copied since is read again from the start, and the index\nkeeps the first record of an id. Two names for one directory are one directory, compared where\nthe paths are resolved and through any symbolic link.\n\nThe install scripts put asz-changes beside asz from an archive that\nholds it, as Homebrew and apt do, and asz alone from an older version\u0027s\narchive, which holds the recorder under its old name. The CI harness\nthat follows the install pages expected the script to leave the\nrecorder to the plugin installer, and expects both now.\n\nSmaller: asz-changes says its own name in its help and its version;\na scenario\u0027s coverage check judges each change file against the cursor\nit was landed behind, as collection does, so removal no longer waits\nfor ever on a session with two plugin directories; a failing Python\nsuite fails make check instead of reporting Python as missing;\nasz.yaml writes headers out and its test reads the file\u0027s keys rather\nthan the loaded values; and the README, the configuration, install,\nrelease and adapter pages say what 0.5.0 does."
    },
    {
      "commit": "8169164f82b9ec70da46afa2aa5e25e66fab370e",
      "tree": "6d2d920357dae53f89c37967861ee54485edd734",
      "parents": [
        "3a75732afcb39286fea141ac08b11906f9d8a6fc"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 21 15:04:33 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 21 15:04:33 2026 +0800"
      },
      "message": "Land what each LangChain model call was sent, beside the conversation (#29)\n\nA call\u0027s record keeps what the model said. What it was told - the whole\nmessage list, sent again on every call - is the evidence the continuity\ncheck between calls runs on, the nested agent\u0027s own opening prompt, and\nthe one thing that tells a tool that summarised some text from one that\nsummarised the conversation and started again from the summary. Without\nit a LangChain conversation was thin in exactly those places, and the\nadapter page said so.\n\nIt lands now as a provider body, cut against what the session already\nholds by pkg/providerbody, the same mechanism as Claude Code\u0027s bodies. A\nrequest is landed from the first arrival of a run that carries inputs,\nwhich never change; a response only from the arrival that carries the\nrun\u0027s end; a repeat is a repeat. Measured on the captured corpus with\nevery call\u0027s inputs and outputs: a twenty-turn conversation lands at 13%\nof what it was on the wire, bodies included, because each request shares\nits front with the one before; a conversation of a few short calls lands\nat about a third, because the note that says how to rebuild a body costs\nmore than a small body saves. The page carried an estimate of twelve\ntimes for landing it whole; the measurement replaces it.\n\nBoth bodies of a call name the call, and the assembler joins them by\nit. On this wire a request knows its call - it is the run\u0027s own id -\nwhere a request Claude Code writes names only the request before it and\nits prompt. The assembler joins a request by the call it names when\nexactly one names it, and a request that names none as it did. So the\nreceiver keeps and derives nothing about the order of a stream\u0027s calls.\nIt did, twice: a cursor that moved as each request body landed, then the\norder the calls\u0027 first records landed in, kept in the session\u0027s shape and\nthen read from the session\u0027s index. Each disagreed with the assembler in\nsome schedule - a run\u0027s completion arriving before its inputs, a session\nlanded before the copy existed, a call whose first fragment landed in\nmain before its ancestry arrived, a request held for a later pass and\nthen replayed - and each time a request joined another call. The run id\nis evidence; an order is an inference, and the assembler\u0027s is the only\none.\n\nA run delivered again with other bytes is not landed a second time and is\ncounted, so a disagreement is not hidden. A nested stream\u0027s records name\nthe tool they ran inside as their prompt, not the trace, so every stream\nof one trace does not share the trace as its prompt. With it, every call\nin every stream of the subagent capture carries what it was sent.\n\nOn this wire a body is the framework\u0027s view of what it sent, in\nLangChain\u0027s serialized message form, and not the bytes the provider\nreceived. The manifest says which adapter it came from.\n\nprovider_bodies on the langsmith-ingest adapter is on by default and\nturns it off."
    },
    {
      "commit": "3a75732afcb39286fea141ac08b11906f9d8a6fc",
      "tree": "7660bb52362af2501f41ce993b0beb88845fce2c",
      "parents": [
        "02a1ff1af71d09bd90211da8fba5c878966e0a1a"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 21 11:48:53 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 21 11:48:53 2026 +0800"
      },
      "message": "Observe LangChain and LangGraph agents (#28)\n\nA LangChain or LangGraph application already sends its whole run tree\nsomewhere: its tracing client posts every run, with the prompts, the tool\ncalls and the results, to whatever endpoint it is told. asz becomes that\nendpoint. Nothing in the agent changes but one environment variable.\n\n  asz collect          a receiver for the tracing client, and the pipeline\n  asz view             the conversations it assembled\n\nWhat arrives is runs, not a conversation, so the work is in reading them:\n\n- A record is an arrival, not a run. A run is posted when it starts and\n  patched when it ends, so the same run reaches asz more than once and each\n  arrival is its own record. Only the arrival carrying the end carries\n  content - otherwise a post and a patch both holding text emit the\n  assistant message twice, in a round that calls itself verified.\n- An update carries only what changed. Ending a run by hand sends its id and\n  its outputs, and nothing else: not the trace, not the project, not the kind\n  of run. So the runs that have started and not ended are remembered, and an\n  update is completed from what its own start said.\n- What opened a turn is the last thing asked, not everything in the input.\n  An application that keeps its own history sends the whole conversation, and\n  reading every human message in it reported an earlier turn\u0027s question as\n  though it had just been asked, every turn.\n- A nested agent is its own execution stream. A tool whose implementation\n  runs an agent produces model calls that continue nothing in the caller\u0027s\n  message list. The boundary is the tool run: work below one is that tool\u0027s,\n  while the call and its result stay with the caller, which is the shape a\n  Claude Code sub-agent already has.\n- A tool that makes one model call is not a second agent. Its prompt does\n  not continue the caller\u0027s either, so it gets a stream of its own for the\n  continuity check - but the stream is auxiliary, the call stays a tool\n  step, and it is not counted as a subagent. What tells the two apart is\n  what ran directly under the tool: a chain of any kind is a program, only\n  model calls are a plain call. This is the first use of the auxiliary role\n  the model reserved, and it is defined now.\n- The collector indexes what it lands. The pipeline parses from the index\n  and never extends it, so a conversation was parsed once and then frozen:\n  every later turn landed, was never indexed, and never reached the chain.\n  Every test had parsed a session once, after everything had landed, which\n  is the one case that could not show it.\n- Nothing lands from a request that cannot land whole. Every record is\n  encoded once, to nowhere, before any file or directory is created, so a\n  field that is valid on its own and impossible inside a record leaves no\n  half-landed session behind. An index that disagrees with its own state\n  is built again rather than extended, so a crash between writing the one\n  and saving the other cannot count a record twice. A link landed again to\n  say something else is a different record, so the index cannot keep the\n  earlier one and drop the correction.\n- Every writer of a session\u0027s index takes it through one guard. A crash\n  between writing the index and saving its state leaves an index that\n  already holds what the state says it does not, and a record with no id\n  of its own - a name - is not told apart from its copy when it is indexed\n  again. A guard in one collector was defeated when another ran first after\n  the crash and saved counts that matched. An index that disagrees with its\n  saved state is built again by whichever collector meets it.\n- Every session of a request is checked before any is landed, so a request\n  carrying two conversations lands neither when one cannot land, instead of\n  landing the first and never reporting it.\n- A conversation\u0027s talks come out in the order they happened. They were\n  ordered by landed position, which orders records within one stream and\n  means nothing across streams, so a sub-agent\u0027s turn came out before the\n  turn that delegated to it. The checked-in view example had the same\n  fault.\n- A graph\u0027s own runs land trimmed. They are 30 of 39 runs and 74.4% of the\n  bytes in one capture, because each repeats content its model call already\n  carries. A decorated function is not the framework, and its arguments and\n  return value are kept: they live nowhere else.\n- Ownership is a namespace, never a bare thread key, and a trace that\n  supplies none is not given one.\n- A conversation is named by the first question asked in it, or by the run\n  that began it when it began without words. Nothing on this wire carries a\n  name: the client names runs, and those name the program.\n- A batch can arrive before the one carrying the runs it ran inside, because\n  the client sends with several threads at once. Placing it then would give\n  one agent\u0027s work to another, so it waits until its ancestry arrives, and\n  is landed anyway once it has waited as long as it may.\n\nThe file changes a tool made are captured too, by a Python callback handler\nthat drives the same recorder the Claude Code plugin does. The two halves\nmeet only in the name of the session directory, so both derive it by one\nrule, from one table, checked in both languages.\n\nEverything here was measured against a running agent. The demo application\nunder tests/apps/langchain needs no API key and no network; its captures are\nthe fixtures, and every number in the documentation comes from them.\n\nAlso in this change, because it is the first runtime after Claude Code:\n\n- The recorder is not Claude Code\u0027s. Its binary is asz-changes, its adapter\n  is changes, and the plugin is file-changes. Before 1.0, two names for one\n  thing cost more than the upgrade does.\n- The page\u0027s vocabulary follows the root rather than the binary, so a\n  conversation is described in the words of the runtime that produced it.\n  A session\u0027s conversation decides that, not whichever file landed first."
    },
    {
      "commit": "02a1ff1af71d09bd90211da8fba5c878966e0a1a",
      "tree": "98d924b1ea6f58aea62b35f097bfc56e848ec901",
      "parents": [
        "1ab1efc0a81e289844792751c5d691f7f08a4cfb"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 18 14:19:53 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 18 14:19:53 2026 +0800"
      },
      "message": "The apt skill installs certificates before it checks the redirects (#26)\n\nThe redirects of the newest version lead to https on www.apache.org, and\ndebian:stable and ubuntu:24.04 carry no certificate store, so apt in the\ncheck reported a certificate error while the index and the redirects were\nright."
    },
    {
      "commit": "1ab1efc0a81e289844792751c5d691f7f08a4cfb",
      "tree": "62219efe6f8dc29b016a2a95dc56f1b91683843f",
      "parents": [
        "04c83b66af5bf1fc4e936e4dff00dcfffe5875a0"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 18 14:08:37 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 18 14:08:37 2026 +0800"
      },
      "message": "Tell Homebrew users to trust the tap (#27)\n\nHomebrew 7 loads a tap that is not one of its own only after brew trust,\nand it matches a user/repository trust entry only against a tap at its\ndefault remote. This tap is at a custom remote, because the repository is\nnot named homebrew-skywalking-ai-sessionizer, so the trust entry has to be\nthe URL. Without it, the tap command a user runs stops with \"Refusing to\nload formula ... from untrusted tap\", and the tap is not created.\n\nChecked with Homebrew 7.0.1: brew trust by URL, then tap, then the four\nformulae of 0.4.0 resolve and download."
    },
    {
      "commit": "04c83b66af5bf1fc4e936e4dff00dcfffe5875a0",
      "tree": "304c53f3c7e80b78bdbe7aa1f5a3757c788b40be",
      "parents": [
        "cc34795cd9888a689d27f059ac7ad5637482017a"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 18 11:47:19 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 18 11:47:19 2026 +0800"
      },
      "message": "Homebrew formulae for 0.4.0, and keep the Debian packages off the downloads page (#25)\n\n* Homebrew formulae for 0.4.0\n\nFormula/ is the tap. 0.4.0 is its first version: asz and asz-claude-code\ninstall it, and asz@0.4.0 and asz-claude-code@0.4.0 keep it installable\nafter a later version moves the first two on.\n\nEach formula downloads the voted binary package for the machine from the\nGitHub release of v0.4.0, with archive.apache.org as its mirror, and names\nthe sha256 of the voted file.\n\n* Keep the Debian packages off the downloads page\n\nThe page listed each .deb beside the archive of the same binaries. The\nDebian packages stay in the release directory with their signatures and\nchecksums, and apt installs them from the repository on the website, so\nthe page lists the source package and the binary archives only."
    },
    {
      "commit": "cc34795cd9888a689d27f059ac7ad5637482017a",
      "tree": "4fe8cf84965b8b78cc2b3c7171e70bed6c362255",
      "parents": [
        "62af8f0c54a33e64e92143f444a55712f85e8fd3"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 20:30:36 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 20:30:36 2026 +0800"
      },
      "message": "Prepare the 0.4.0 candidate and open 0.5.0 (#24)\n\n* Prepare the 0.4.0 candidate\n\nThe changelog of 0.4.0 loses its in-development note and keeps its\npath, docs/en/changes/changes.md, which the menu and the welcome page\nlink. The tag goes on this commit, as the candidate for the vote.\n\n* Open 0.5.0\n\nThe changelog of 0.4.0 moves from changes.md to its own page,\nchanges-0.4.0.md, and the version is listed under Changelog, right\nafter Current Version. changes.md is the changelog of 0.5.0 from here.\nCurrent Version and the welcome page still link changes.md."
    },
    {
      "commit": "62af8f0c54a33e64e92143f444a55712f85e8fd3",
      "tree": "c4b162434f40054bbccf567bc83424e1a6668937",
      "parents": [
        "7c74b62d84488c293fe37c3b795e470d1b7efa3a"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 20:01:11 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 20:01:11 2026 +0800"
      },
      "message": "Fix what the 0.4.0 release audit found (#23)\n\n- The GitHub release text links the changelog\u0027s other pages at the tag.\n  The changelog links them relative to itself, and on the 0.3.0 release\n  page such links resolved under /releases/ and found nothing.\n- The changelog names the renderer\u0027s final pin, Horizon 53c1f63b, and adds\n  what #13, #14 and #15 changed: the Prompt tab reading a message in either\n  spelling, and the reasoning signature a scenario build records. It says\n  provider bodies land in files of at most max_delta_bytes, gives the\n  removed manifest script its real path, and records the tools/ split.\n- Install gives the binary package for the machine it runs on. The\n  example named the Linux x86-64 package under \"On macOS or Linux\".\n- Upgrading the plugin by hand says to move asz-claude-plugin first. The\n  hooks run the binary on PATH, which the marketplace commands leave alone.\n- The release guide counts the Debian packages CI attaches, lists them in\n  the vote mail example, and says how to prepare the same version again\n  after the prepare pull request has merged.\n- prepare refuses a detached HEAD, where it would push the tag and then\n  have no branch to raise the pull request against. publish counts the\n  Debian packages among the files it checks."
    },
    {
      "commit": "7c74b62d84488c293fe37c3b795e470d1b7efa3a",
      "tree": "1ca9c0447ea460fd39406318135cf65ff0c9aa2a",
      "parents": [
        "33217518875452a5a4cedc85e04b41e872d636ce"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 18:45:01 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 18:45:01 2026 +0800"
      },
      "message": "Debian packages, the homebrew and apt skills, and tools separated into release and test (#22)"
    },
    {
      "commit": "33217518875452a5a4cedc85e04b41e872d636ce",
      "tree": "7186c836e97df53df983c2a2f5b56f475256a218",
      "parents": [
        "e55c406df8909e91243aed365a34d3be4858f4ec"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 16:35:11 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 16:35:11 2026 +0800"
      },
      "message": "Keep every release installable with Homebrew, and add the binary-distribution skill (#21)\n\nA formula names one version, so once asz.rb moves to the next release the\nprevious one could no longer be installed. Each release now also gets\nasz@VERSION and asz-claude-code@VERSION, which Formula/ keeps. They are\nkeg-only, as Homebrew wants for an alternate version, so installing one\nbeside the current formula does not replace its binary on PATH.\n\nHomebrew relates asz@VERSION to asz only when VERSION is digits and dots.\nWith the CI version 0.0.0-ci it did not, linked asz@0.0.0-ci over asz, and\nthe install failed. Every Apache release is digits and dots, so the\ngenerator writes versioned formulae only for such a version, the check\nrefuses any other, and CI builds its packages as 0.0.0. The check also\nremoves a formula whose install stopped part way, which it left behind.\n\ntools/homebrew-formula.sh [--from DIR] [--check] VERSION... writes the\nformulae of released versions into Formula/. It downloads each version\u0027s\npackages from downloads.apache.org, or archive.apache.org for an older one,\nholds them to their sha512, runs tools/homebrew-check.sh first with --check,\nand moves asz.rb and asz-claude-code.rb only forward, so adding an older\nversion never moves them back. Tested with two local versions and a re-add\nof the older one, and against the real sites with 0.3.0, whose old package\nlayout it refuses, and 0.2.9, which was never released.\n\n.claude/skills/binary-distribution runs it for one version or several,\nchecks each version is released first, and opens the pull request."
    },
    {
      "commit": "e55c406df8909e91243aed365a34d3be4858f4ec",
      "tree": "893762cb8e6a1727d1f7e39d56aaa8d12a736ab6",
      "parents": [
        "41c591a76d90fc97f873a42165a1275402b813eb"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 15:50:52 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 15:50:52 2026 +0800"
      },
      "message": "Make the setup pages say how to install and use asz, and move the rest (#20)\n\nThe setup pages had grown into design notes: release status, version\nhistory, why each download source was chosen, what CI ran on which\nrunner, and every rule behind every report. A person installing asz had\nto read past all of it to find the command.\n\nEach setup page now gives the commands and what a person needs to act on\nthem, and runs 3,934 words where it ran 20,055. What explains how things\nwork inside moved, as it was, to pages of its own: Claude Code Plugin\nInternals and Claude Code Provider Bodies Internals under adapters, OTLP\nRecords under formats, and Command Line Internals under guides.\n\nQuick Start starts from an installed asz rather than a checkout. It says\nthat asz keeps its data in ./data under the directory it runs in, reads\nasz.yaml there, and how to download the default asz.yaml of the version\nasz version prints. The container image page keeps how to run it; how\nimages are tagged and published is in the release guide. The README\u0027s\nquick start installs with the script and runs asz server.\n\nclaudecodecheck follows the pages\u0027 new headings, and drops the\nVERSION\u003d\u003cversion\u003e line a block now shows, since it sets the version\nitself. It passed on macOS against the rewritten pages. Every relative\nlink and anchor in docs/ and the README resolves."
    },
    {
      "commit": "41c591a76d90fc97f873a42165a1275402b813eb",
      "tree": "fe9b45f4197d5540d348555c68054f975b2d3074",
      "parents": [
        "6923287aa5d701e753f452e313cf2bcfd21fdebd"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 15:48:21 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 15:48:21 2026 +0800"
      },
      "message": "Homebrew: brew install asz and asz-claude-code from this repository as the tap (#19)\n\n* Write a Homebrew formula for each install: asz and asz-claude-code\n\nThe install pages have two installs, asz and the Claude Code plugin, and\nthe release wrote one formula, skywalking-ai-sessionizer, with both\nbinaries. It now writes asz.rb, which installs asz, and\nasz-claude-code.rb, which installs asz-claude-plugin and whose caveats give\nthe two commands that install the plugin into Claude Code. From a tap,\nthat is brew install asz and brew install asz-claude-code.\n\nNeither name is taken in homebrew-core or its casks, read on 2026-09-17.\nBoth formulae download the same package, which Homebrew caches once. In a\nlocal tap they pass brew style and brew audit --strict.\n\n* Make this repository the Homebrew tap, and check the formulae in CI\n\nAfter each release the two formulae go to Formula/ on main, where Homebrew\nlooks first, and people install with:\n\n  brew tap apache/skywalking-ai-sessionizer https://github.com/apache/skywalking-ai-sessionizer\n  brew install apache/skywalking-ai-sessionizer/asz\n  brew install apache/skywalking-ai-sessionizer/asz-claude-code\n\nInstalling by the full name trusts that formula. The formulae carry the\nvoted packages\u0027 sha256, so they are committed after publish, and\n.gitattributes keeps Formula/ out of the source package, which would\notherwise carry the formulae of the version before it. They get the\nlicense header every file in the tree has.\n\ntools/homebrew-check.sh writes the formulae from a set of packages into a\nlocal tap, runs brew style and brew audit --strict on them, installs and\ntests both from the local packages, and removes what it installed. The new\nhomebrew job runs it on macOS on packages built as 0.0.0-ci. It passed here\non 0.4.0 and 0.0.0-ci packages.\n\nThe install page shows only channels a person can use: the Homebrew\ncommands, without the placeholder tap name, and no longer the Scoop and\nwinget sections, which have nothing published. The notes on what each\nversion up to 0.3.0 did, and on the releases before the first vote, leave\nthe setup pages."
    },
    {
      "commit": "6923287aa5d701e753f452e313cf2bcfd21fdebd",
      "tree": "0ebfae2d950279ac1deb28cbb4051403c66c68a7",
      "parents": [
        "022e7288c166dd6388eaf5fdd8ddb8f988359b1e"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 14:58:17 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 14:58:17 2026 +0800"
      },
      "message": "Install the Claude Code plugin from a marketplace, and test it with Claude Code on every platform (#18)\n\n* Install the Claude Code plugin from a marketplace, and its binary beside asz\n\n0.3.0 told users to run claude --plugin-dir on the packaged plugin.\nClaude Code keeps --plugin-dir for one session, so the next plain claude\nrecorded no change and reported nothing.\n\nThe repository now has .claude-plugin/marketplace.json. Users add it at a\nversion\u0027s tag and install asz-changes from it, which lasts across\nsessions. The manifest and the hooks move to plugins/claude-code/plugin/,\nwith their own LICENSE and NOTICE, so a marketplace install copies no Go\nsource. Neither manifest sets a version: a version pins the plugin until\nsomeone changes it, and without one Claude Code uses the tag\u0027s commit.\n\nThe hooks run asz-claude-plugin by name from PATH, as Anthropic\u0027s language\nserver plugins run their servers. A binary inside the plugin would have to\nbe committed for every platform, and a source release carries no compiled\nfile. So each binary package holds asz and asz-claude-plugin side by side,\nmake build writes both to bin/, and the package smoke test, the release\ncandidate check, CI and the Homebrew, Scoop and winget manifests follow.\n\nInstall gives two copy-and-run blocks, one for macOS and Linux and one for\nWindows. The reader sets the version. Each block checks the package against\nits sha512 from downloads.apache.org, checks that both binaries start, and\ninstalls them where the Claude Code installer puts claude.\n\nMeasured with Claude Code 2.1.274 in its own configuration directory:\n- The marketplace installs from a git URL at a tag with --sparse, and the\n  plugin cache holds only the manifest, the hooks and the legal files.\n- With the binary on PATH, a headless session recorded a shell command\u0027s\n  file, and asz collect landed it. Without it, Claude Code reported\n  \"Executable not found in $PATH\" and the command still ran.\n- Removing the marketplace deletes the plugin\u0027s data directory, and with\n  it the records asz has not collected. The documented upgrade uninstalls\n  with --keep-data first, which kept them.\nNeither install block has run on Windows.\n\n* Run the install pages with Claude Code on every package platform in CI\n\nClaude Code had run the plugin\u0027s hooks on macOS only, and never on Linux\nor Windows. On Windows it is unknown whether a hook naming\nasz-claude-plugin finds asz-claude-plugin.exe.\n\ntools/claudecodecheck follows the install pages on the machine it runs\non. It reads each block from the page and points only its download\naddresses at a server on 127.0.0.1, which holds the package, a git\nrepository with the marketplace at two tags, and a stand-in for the\nmodel\u0027s API. It runs the Quick install block in every shell there is for\nit, the plugin\u0027s install commands, a headless session the plugin must\nrecord and asz must collect, a session without asz-claude-plugin on PATH,\nand the Upgrade block, after which the plugin\u0027s data must remain and the\nnext session must be recorded.\n\nThe new claude-code job runs it with Claude Code 2.1.274 on the six\nrunners the packages job uses. The version is fixed, so a Claude Code\nrelease cannot fail a pull request that did not change. The prerelease\nand the required check wait for the job.\n\nOn 2026-09-17 the check passed on macOS on Apple silicon, and on Linux on\nARM 64 in Debian 13.6 and in Alpine 3.22.5, with Claude Code\u0027s musl build.\n\n* Install asz and the Claude Code plugin with a script each\n\nasz, the collector, and the Claude Code plugin are two installs, and the\npages now say so. Each has a script under install/, run from the version\u0027s\ntag in one command:\n\n  curl -fsSL \"https://raw.githubusercontent.com/apache/skywalking-ai-sessionizer/v$VERSION/install/asz.sh\" | sh -s -- \"$VERSION\"\n  curl -fsSL \"https://raw.githubusercontent.com/apache/skywalking-ai-sessionizer/v$VERSION/install/claude-code-plugin.sh\" | sh -s -- \"$VERSION\"\n\nand install/asz.ps1 and install/claude-code-plugin.ps1 for Windows. The\naddress names the tag, so the script that runs is the one released with\nthe version.\n\nBoth download the package through the mirror selector and its sha512\nfrom downloads.apache.org, stop unless they match, check that their\nbinary starts, and install it where the Claude Code installer puts claude.\nThe plugin\u0027s script then adds the marketplace at the tag and installs the\nplugin. When the plugin is at another tag, it uninstalls it with\n--keep-data before it moves the marketplace, because removing the\nmarketplace deletes the records asz has not collected. It stops when the\nplugin is installed at another scope, and copies a 0.3.0 settings.yaml\nonce. Run at the same tag, it changes nothing.\n\nThe long copy-and-run blocks leave the pages. The manual commands stay,\nunder By hand and Upgrade.\n\ntools/claudecodecheck now runs both one-line commands as the pages write\nthem, the plugin\u0027s upgrade by hand and by its script, the script again at\nthe same tag, and the By hand commands. Its first form looked for VERSION\nin the PowerShell block, which spells it $Version, so the Windows jobs\nstopped before Claude Code ran.\n\n* Work under the long name of a Windows temporary directory in claudecodecheck\n\nA Windows runner\u0027s TEMP is C:\\Users\\RUNNER~1\\... Claude Code 2.1.274\nrefused the session\u0027s shell command, a write under that path, as a\nsuspicious Windows path pattern that needs manual approval. The plugin\u0027s\nSessionStart hook had run by then and exited 0 on windows-amd64 and\nwindows-arm64, so the hooks find asz-claude-plugin.exe by its bare name.\n\n* Say what Claude Code did with the plugin on Windows\n\nThe claude-code job passed on windows-latest and windows-11-arm. Both\ninstall scripts ran in Windows PowerShell and PowerShell 7. The hooks name\nasz-claude-plugin without .exe, and they started asz-claude-plugin.exe:\nthe plugin recorded the shell command\u0027s file, and with the binary off the\nPath Claude Code reported \"Executable not found in $PATH\" and the command\nstill ran. The runners have Git Bash, so the shell tool was Bash; a\nmachine without it is untried.\n\nThe pages and the Scoop and winget notes said Claude Code had never run\nthe hooks on Windows, and the plugin page told a Windows user how to find\nout. They now say what ran.\n\n* Take a version the download site no longer holds from the archive\n\ndownloads.apache.org holds only the newest release, so the install\nscripts could not install any other version. archive.apache.org keeps\nevery version, but it slows down and then bans heavy use, which an install\nscript run by many people and CI jobs is.\n\nSo each script asks the download site for the version\u0027s sha512 first.\nWhen it is there, the package comes through the Apache mirrors, as before.\nWhen it is not, the sha512 and the package both come from the archive.\nThe archive is run by Apache, not a mirror, so its sha512 can be trusted\nas the download site\u0027s is.\n\nclaudecodecheck serves the first two versions on the download site and\nall three in the archive. It checks that the newest version comes through\nthe mirrors, and that asz and the plugin\u0027s upgrade take the third version\nfrom the archive.\n\n* Take the archive only when the download site says the version is not there\n\nThe install scripts took any failure on downloads.apache.org as the\nversion having left it, and went to the archive. Run against the real\nsites for 0.3.0, the download site\u0027s TLS handshake failed, the archive\u0027s\nfailed too, and the script said neither site had the package, which was\nnot true.\n\nA 404 now sends the script to the archive. Any other answer, or no\nanswer, stops it with what went wrong: \"cannot reach downloads.apache.org\",\nwith curl\u0027s error above it, or the status the site gave."
    },
    {
      "commit": "022e7288c166dd6388eaf5fdd8ddb8f988359b1e",
      "tree": "22b48277067694e6b5f9f49506812cf08c37c6fd",
      "parents": [
        "ac0c114d4efd884028269bf4d3ec25089ab1096e"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 11:59:15 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 11:59:15 2026 +0800"
      },
      "message": "Write the website release for data/projects.yml (#17)"
    },
    {
      "commit": "ac0c114d4efd884028269bf4d3ec25089ab1096e",
      "tree": "cca3a3c2077c15279b06f698eabed6b0414b275c",
      "parents": [
        "ddc9abd9b383bc9114b30e162bc0783757106a47"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 10:09:58 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 10:09:58 2026 +0800"
      },
      "message": "Ask at publish whether a release becomes the latest (#16)\n\nPromotion did not move GitHub\u0027s Latest label. CI creates the prerelease\nwith --latest\u003dfalse, and publish promoted it with --prerelease\u003dfalse\nonly, so the label stayed on the older release until someone moved it by\nhand.\n\npublish now asks, before anything moves, whether the version becomes the\nlatest release, and promotes with that answer. The answer offered is yes\nfor a version newer than every full release, and no otherwise, such as a\npatch of an older line. --latest and --not-latest answer without asking.\nAn unclear answer, or both options, stops the command. A run after the\npromotion refuses either option, because that run already decided.\n\nThe latest image tag now follows GitHub\u0027s latest release, so one answer\nmoves both. Before, the image job took the highest version on its own,\nwhich could disagree with the label."
    },
    {
      "commit": "ddc9abd9b383bc9114b30e162bc0783757106a47",
      "tree": "bba4ee66b7daf9a21589f9cb60d18f7cfde8607a",
      "parents": [
        "97c2cc0466b0eb5853c138ac66a17d34f432aa46"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 10:05:06 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 10:05:06 2026 +0800"
      },
      "message": "Draw a compaction as a compaction in asz view (#15)\n\nThe embedded renderer moves to Horizon 53c1f63b (#166). The one before it\ncompared a message of one text block as two different messages depending on\nwhether it was the newest, since the runtime sends it as a list while it is and\nas a plain string after. So it reported an ordinary growth step as a rewritten\nhistory: on one real 94-call conversation, 60 of 62 of them.\n\nThe prompt tab now reads both spellings as one message, and says which of two\nthings happened when the history does change: the context was replaced by a\nsummary, with how many messages became how many, or, when the history changed\nwithout getting shorter, a warning.\n\nasz view and the SkyWalking UI draw a conversation from the same renderer, so\nthis keeps the page beside the storage root from warning where the UI does not."
    },
    {
      "commit": "97c2cc0466b0eb5853c138ac66a17d34f432aa46",
      "tree": "c06e705ca17aea760f9706edd610cdca9bc6cb0f",
      "parents": [
        "0f9612b060390f36e43c753bfeb087cc0500efb5"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 09:29:35 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 09:29:35 2026 +0800"
      },
      "message": "Say that a message has two spellings, not only that the marker moves (#14)\n\nThe provider bodies page said earlier messages come back unchanged apart from\nthe cache marker. A message of one text block also changes shape: it is sent as\na list of blocks while it is the newest, and as a plain string once the marker\nhas left it. A reader comparing one request with the next has to treat both\nspellings as one message, and a reader who took the page at its word reported a\nhistory that merely grew as one that had been rewritten.\n\nThe page now describes both, and what a compaction does to the list, which is\nthe case that really does rewrite it."
    },
    {
      "commit": "0f9612b060390f36e43c753bfeb087cc0500efb5",
      "tree": "0cd9e49895a356fcc597d38dccd9e9a51a486ab0",
      "parents": [
        "acd2f52c0c0a816f4a5483ec8aeb871a739dfda3"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Thu Sep 17 08:30:41 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Sep 17 08:30:41 2026 +0800"
      },
      "message": "Record the dropped reasoning signature whichever way the reasoning landed (#13)\n\nA thinking block always carries a signature, and the adapter drops it: a\nprovider verifies it and a reader cannot read it. A claude-code build writes\none on every thinking block, because a runtime does. The sd writer recorded\nthe drop only for reasoning whose text was already gone, so a block that kept\nits text landed with the drop in one format and without it in the other, and\nthe same scenario no longer folded to the same conversation through both\nwriters.\n\nOnly `records_match` could see it, and nothing exercised it. Every scenario in\nthe tree writes `thinking: unavailable`, which is the one branch that recorded\nthe drop, so the branch with text had no cover at all. `reasoning-text.yaml`\nwrites both shapes in one session. It fails without this change and passes with\nit."
    },
    {
      "commit": "acd2f52c0c0a816f4a5483ec8aeb871a739dfda3",
      "tree": "579791259ed1c7496fa4e5af2324051922f92b47",
      "parents": [
        "747d83c9615b19a1edc7d79606f2d4dfbefc153b"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Wed Sep 16 23:17:51 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Sep 16 23:17:51 2026 +0800"
      },
      "message": "A scenario writes the prompt its agent sends, and asz view draws it (#12)\n\nWith `provider_bodies: true` a scenario already wrote the request and the\nresponse of every call, and their messages were already real. Two fields inside\nthe request were not. The system prompt was one sentence repeated eighty times,\nand the tool list was Read and Bash whatever the agent did, so the showcase drew\na calendar assistant as a coding agent. The response carried no usage at all,\nthough every call in every scenario already declares one.\n\n`system_prompt` and `tools` say what a stream sends: on the scenario for the\nmain stream, and on an `agent`, `skill` or workflow child for its own, since a\nnarrower set is the reason to start one. A scenario writes its own and shares\nnone with another: a request body is read as a document, and a borrowed\ndescription describes the wrong agent, the same tool name meaning one thing to a\nrelease manager and another to an inbox assistant. They cannot be derived from\nthe calls either, because a description is prose about what a tool is for, which\nno call carries. A scenario that writes neither keeps the stand-in, which is\nfiller sized to make a body worth cutting rather than something to read, and the\nprovider-bodies scenarios pin a file count against it.\n\nWriting one without the other is refused. Each falls back on its own, so half a\ndeclaration would leave an authored prompt beside the stand-in\u0027s Read and Bash,\nwhich is the very thing writing them is meant to stop. Written and empty is not\nunwritten: a stream may advertise no tools at all, and sends an empty list.\n\nA stream that writes tools must advertise every tool it calls, starting a child\nincluded, since the block is named Agent, Skill or Workflow. A model cannot call\na tool the request never offered it, so a scenario that advertised two and called\nnine would be a request and response pair that cannot happen. A value YAML writes\nas a plain scalar but JSON refuses, an infinity or a NaN, is refused when the\nscenario loads, naming the tool: the body writer cannot report it, so it used to\nleave an empty body and surface much later as a digest that did not match.\n\nThe response carries the call\u0027s own usage, the numbers its transcript already\nrecords, so a reader comparing the two sees one number.\n\nThe embedded renderer moves to Horizon 0ce1f8d6, which added the inspector\u0027s\nPrompt tab, and the page gains the route that tab reads. `GET\n/api/c/{id}/files?session\u003d\u0026seq\u003d` returns landed files by sequence, at most\nthirty-two and sixty-four megabytes a request, as base64 in a JSON array. The\nrenderer offers the tab only to a host that can read files, so the pin alone\nwould have moved asz view to a renderer whose Prompt tab it could never show,\nand the page beside the storage root would have drawn less than the SkyWalking\nUI does. A repeated sequence is read once. A file that is gone is left out, as an\nunknown sequence is, and the renderer reports which it did not get; a permission\nor an I/O failure is reported instead, so a reader can ask again rather than\nbeing told the file is absent.\n\nThe scanner takes regular files only. A landed file is written once and left\nread-only, so anything else carrying a landed name is not one: a symlink in a\nroot from somewhere else would otherwise have been read and served as though it\nwere landed data, whatever it pointed at. That closes the same hole on the record\nroute, where it was older than this change."
    },
    {
      "commit": "747d83c9615b19a1edc7d79606f2d4dfbefc153b",
      "tree": "42202b87049e05dad10e42a60bb7d841d5194d3c",
      "parents": [
        "73733153a851006d33bfdde587770e619849090f"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Wed Sep 16 12:53:44 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Sep 16 12:53:44 2026 +0800"
      },
      "message": "Carry the provider body join in the round (#11)"
    },
    {
      "commit": "73733153a851006d33bfdde587770e619849090f",
      "tree": "36b2b438b23cb63fdb23eff9336b60a03e6f5127",
      "parents": [
        "e6ef4344c8008c2ca4417bd3a1773dfcbcc455dc"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Sep 15 21:10:50 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Sep 15 21:10:50 2026 +0800"
      },
      "message": "Collect Claude Code\u0027s provider bodies, with repeats taken out (#10)\n\n* Collect Claude Code\u0027s provider bodies, with repeats taken out\n\nClaude Code writes every request it sends its model provider, and every\nresponse, when OTEL_LOG_RAW_API_BODIES\u003dfile:\u003cdir\u003e is in its environment.\nA request holds what no transcript records: the system prompt, the tool\nschemas, the injected reminders and the system messages between the\nothers. Every call sends its chain\u0027s whole message list again, so most\nof a body is bytes the session already holds.\n\n- pkg/providerbody cuts a body into pieces, each tool definition and\n  every string of 1 KiB or more, and copies the front a body shares with\n  the previous body of its chain. A record keeps only what is new; its\n  last part, a provider_body/1 manifest in the model\u0027s words, rebuilds\n  the body byte for byte. References are record ids and digests, which\n  asz repack leaves valid, and they may reach into earlier files. Two\n  captures of Claude Code 2.1.260 landed at 25.5% and 21.6% of the bytes\n  written, and all 42 bodies rebuilt exactly.\n- The claude-code-provider adapter reads the flat directory, finds each\n  body\u0027s session from the body and the landed transcripts, and lands a\n  session\u0027s new bodies under \u003csession\u003e/provider_body/, cut at\n  max_delta_bytes. What it knows of Claude Code\u0027s names stays in the\n  adapter. A root table, _provider/seen.state, is derived from the\n  landed records. Each record is checked through the writer and reader\n  before it lands.\n- The kind stays out of the fold and out of every index lookup.\n  storage.LandedFiles lists the session\u0027s provider_body directory, and\n  asz verify rebuilds every body. In asz.view an llm.call lists the role\n  and landed ref of its request and response, joined by the bodies\u0027 own\n  ids; a reader loads the provider_body files up to that ref and\n  rebuilds. summary.provider_bodies and summary.captured_prompts count\n  them. On the second capture all 13 calls had both joined.\n- A scenario removal deletes the body files with the session\u0027s other\n  files and drops its lines from the provider table.\n- Scenarios can write provider bodies in both formats. An expectation\u0027s\n  collect.max_delta_bytes goes into the build\u0027s configuration and the\n  check follows it. provider_bodies_rebuild reads each body the way an\n  on-demand reader does, on the root and its repacked copy, and\n  provider_bodies_leave_the_fold compares fold identities.\n\n* Order calls on one record by id in the provider body join\n\nThe view reads the call nodes from a map and sorts them by stream and\nlanded position. Two calls on one record kept the map\u0027s order, and a\nrequest joins the call its order puts first, so the same files could give\ntwo documents. The call id now breaks the tie, as the OAP\u0027s view does."
    },
    {
      "commit": "e6ef4344c8008c2ca4417bd3a1773dfcbcc455dc",
      "tree": "604092825c147dd0062e9e58aa2e0de3f888824f",
      "parents": [
        "9f3666efb7c5700de80a28ae441d1947f23689c9"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Tue Sep 15 13:55:56 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Sep 15 13:55:56 2026 +0800"
      },
      "message": "Collect every 10 minutes by default, and keep 5 seconds for tests (#9)\n\nEvery pass that finds new records lands new files, and a landed file is\none log record when it is sent. At the 5 second default, a watching\ncollector wrote a small file for every stream that moved, every 5\nseconds. Nothing in the docs said to raise it.\n\n- The collector\u0027s default interval is 10 minutes, in asz.yaml and when\n  no configuration is given. asz collect and asz server still make a\n  pass when they start.\n- A scenario build writes interval: 5s into the configuration it\n  creates, so a collector beside a feed picks up each session as it\n  arrives. A directory an earlier build wrote without an interval is\n  brought up to date instead of refused.\n- The configuration page gains a section on choosing an interval, and\n  the quick start, command line, container image and scenario pages,\n  the collect skill and the 0.4.0 changelog say 10 minutes."
    },
    {
      "commit": "9f3666efb7c5700de80a28ae441d1947f23689c9",
      "tree": "8b89237c20c2f5d39beb5d1be0aed178a85a0e7d",
      "parents": [
        "ae84d5c3c08785d8d2d8b9921c07f5c8ed226e7f"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 14 22:49:51 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 14 22:49:51 2026 +0800"
      },
      "message": "Prepare the 0.3.0 candidate and open 0.4.0 (#8)\n\n* Prepare the 0.3.0 candidate\n\nThe changelog of 0.3.0 loses its in-development note and keeps its\npath, docs/en/changes/changes.md, which the menu and the welcome page\nlink. The tag goes on this commit, as the candidate for the vote.\n\n* Open 0.4.0\n\nThe changelog of 0.3.0 moves from changes.md to its own page,\nchanges-0.3.0.md, and the version is listed under Changelog, right\nafter Current Version. changes.md is the changelog of 0.4.0 from here.\nCurrent Version and the welcome page still link changes.md."
    },
    {
      "commit": "ae84d5c3c08785d8d2d8b9921c07f5c8ed226e7f",
      "tree": "4e77ba768d1ee4ef9cca1b7602a13947ffe9d713",
      "parents": [
        "d77d59541e2a117aa154e01a2223d86bc797a245"
      ],
      "author": {
        "name": "吴晟 Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Mon Sep 14 22:30:48 2026 +0800"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Sep 14 22:30:48 2026 +0800"
      },
      "message": "Fix release blockers and stage CI binaries through GitHub prereleases (#7)\n\nStorage\n- Publish a round or a receipt through a temporary file that is synced,\n  made read-only, then installed without replacing an existing file:\n  renameat2 with RENAME_NOREPLACE on Linux, renamex_np with RENAME_EXCL\n  on macOS, MoveFileEx on Windows. A filesystem without an exclusive\n  rename uses a hard link. exFAT on macOS has neither, so it uses an\n  O_EXCL reservation that the complete file replaces. An interrupted\n  write no longer leaves a truncated round that stops every later parse.\n- Nothing asz publishes is empty, so an empty file under a round\u0027s or a\n  receipt\u0027s name is a reservation a crash left. The chain does not count\n  it, and a publication replaces it once it is a minute old.\n- asz repack refuses a populated destination, and never rewrites a\n  session or a chain there.\n\nMetrics\n- Follow a call across any number of landed files. An unfinished call is\n  counted when its final fragment lands, even after the grace.\n- Publish an immutable receipt, holding the exact request and the calls\n  it counted, before the request enters the spool. A retry after a failed\n  progress save uses it, even when new fragments have arrived. Receipts\n  are applied before any file is derived again, and a receipt only moves\n  a series forward, so no two windows of a series overlap.\n- Retry deferred and failed sessions while other sessions keep landing.\n  A session the first pass did not finish keeps that pass\u0027s look-back.\n\nConfiguration and commands\n- A named adapter starts from its own defaults, so omitting enabled no\n  longer disables it. An explicit false or an empty exclusion list stays\n  as written.\n- asz show finds landed .sd files through the shared file locator.\n- The conversation list skips a conversation whose only round file is\n  the temporary file of an interrupted write.\n\nClaude Code plugin\n- Apply the configured roots, exclusions and content size limit to\n  subagent edits and to their manifest updates.\n- The readonly-v2 policy scans commands that can write files or run\n  programs: env and xargs with arguments, writing options of sort, tree,\n  rg, sed, awk, find, git and go, and a leading variable assignment other\n  than a locale or display setting.\n\nRelease\n- tools/release.sh has three commands. prepare tags the candidate, pushes\n  the tag and opens the pull request. CI on the tag push builds and tests\n  every package, then creates the GitHub prerelease and attaches the six\n  binary archives, their checksums, and a readiness marker holding a\n  fingerprint of the verified files. candidate checks those binaries\n  against the run of the tag push, archives the tagged source, signs\n  every package, uploads the candidate to the dev area of\n  dist.apache.org, attaches the source package and every signature to\n  the prerelease, and writes the vote mail. After the vote, publish\n  verifies the signatures, moves the candidate to the release directory,\n  checks every file of the GitHub release against it, and promotes the\n  prerelease, which publishes the image. vote-result and complete are\n  removed. make release runs candidate with --no-upload.\n- A second run of candidate or publish resumes where the first stopped,\n  without signing or moving again. No file on the GitHub release is ever\n  replaced. A KEYS entry that gpg cannot import does not stop either\n  command.\n- prepare stops when gh cannot check the release, before anything is\n  pushed. The latest image tag is no longer dropped silently when the\n  release list cannot be read.\n- Local tar packaging suppresses AppleDouble metadata. Package checks\n  reject ._*, .DS_Store and __MACOSX entries."
    },
    {
      "commit": "d77d59541e2a117aa154e01a2223d86bc797a245",
      "tree": "083868808058229dfb346d725b99d3b5e470e4b9",
      "parents": [
        "61ad7da08c1fd34da5c93b16f04c0d8b629b0e90"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 12 07:50:17 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 12 07:50:17 2026 +0800"
      },
      "message": "Preserve source JSON bytes in landed parts\n"
    },
    {
      "commit": "61ad7da08c1fd34da5c93b16f04c0d8b629b0e90",
      "tree": "5645aac66104cf984e0ac021f45a8004861dae03",
      "parents": [
        "8fd56ff02055644b3a715a467dacc4da9d81597e"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 12 07:45:51 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Sat Sep 12 07:45:51 2026 +0800"
      },
      "message": "Report lost records and preserve unknown source bytes\n"
    },
    {
      "commit": "8fd56ff02055644b3a715a467dacc4da9d81597e",
      "tree": "08fd856dc06ca2617a6310d0b820a5b698e22773",
      "parents": [
        "215e59323d814f8bd88b674f70f885c788812ff1"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 21:26:28 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 21:26:28 2026 +0800"
      },
      "message": "Run a scenario workflow\u0027s children at the same time\n\nThe scenario guide says a workflow starts its children as one batch, and\nthe runtime runs them together: in 165 of the 172 runs with two or more\nchildren in one Claude Code history, the children overlapped in time. The\nplanner started each child only after the one before it had ended, so the\nshowcase drew a review by four agents as four reviews in a row.\n\nEach child now starts one interval after the launch and a moment after the\none before it. The parent\u0027s next step waits for the last child, and the\njournal is kept in time order, as the runtime appends it.\n\nThe push_follows_the_wire check compared record times as text. RFC3339\nwith nanoseconds drops trailing zeros, so 00:00:04Z sorted after\n00:00:04.2Z. It compares them as times now, as the sender already did.\n"
    },
    {
      "commit": "215e59323d814f8bd88b674f70f885c788812ff1",
      "tree": "80438b6451d57086a44288028d5700fa28af85dd",
      "parents": [
        "1150dcd9d3fbf6a513de4cb26d83c9153684fd0f"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 21:06:14 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 21:06:14 2026 +0800"
      },
      "message": "Release the scenario lock at the end of two pipeline tests\n\nWindows cannot delete a file that is still open. Two tests built a\nrefresher directly and never closed it, so the scenario root\u0027s lock file\nstayed open and t.TempDir could not remove the root. The helpers the other\npipeline tests use already close the refresher. Unix lets a locked file be\ndeleted, so only the Windows job failed.\n"
    },
    {
      "commit": "1150dcd9d3fbf6a513de4cb26d83c9153684fd0f",
      "tree": "43d462c3418844347a07059c5f4e5b082ffea64b",
      "parents": [
        "10de72279fbc6b8461450a00ebff7c372fe21878"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 20:55:58 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 20:56:00 2026 +0800"
      },
      "message": "Remove a scenario session once it is sent, by the policy its build chose\n\nA scenario feed writes sessions until it is stopped, and nothing removed\nthem, so a demo\u0027s storage root grew without end. Removal is a policy of\nthe scenario, never an option of the product. asz scenario build takes\n--remove immediately, the default, or --remove with a duration, and writes\nit into a marker for each session after every other file of it. asz\ncollect and asz server remove a marked session at the end of a pass, once\nevery landed file, round and metrics request of it was sent to the\nreceiver they send to and none was rejected. A session no build marked,\nwhich is every real Claude Code session, is never removed.\n\nA crash must never send half a session. The source files go first, so\nnothing lands them again. The session and chain directories are each\nrenamed into _removed/ in one call before they are deleted, so nothing\nparses what is left of them. A pipeline over a scenario root holds a root\nlock, so a second one waits. Parse now takes the chain lock before it\nreads the index. push.state records the endpoint it sent to and the files\na receiver rejected, and an HTTP answer counts as a success only when its\nbody is empty or protobuf.\n"
    },
    {
      "commit": "10de72279fbc6b8461450a00ebff7c372fe21878",
      "tree": "c7dc881622d32d76596af3aba11868c7cab8ca2c",
      "parents": [
        "c1098353406cf3ec7baebcbea82132856e8181c3"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 18:55:18 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 18:55:18 2026 +0800"
      },
      "message": "Check the plugin\u0027s own record in the package smoke, and run its hook without a shell\n\nThe smoke check searched the whole plugin data directory for the file it\nwrote, so the plugin\u0027s own snapshot passed it without any change record. It\nnow reads the output record and needs the change there. Its probes have a\ntime limit, and the checksum and the Windows unpack take the exact file.\n\nhooks.json uses the command\u0027s exec form, which Claude Code runs with no\nshell on every platform. CI runs the plugin and command package tests on\nall three systems, and a new job builds and tests the unpacked source\npackage the way a voter does.\n"
    },
    {
      "commit": "c1098353406cf3ec7baebcbea82132856e8181c3",
      "tree": "9837d9a232eafae5dedec55aa20eb8b4de0c7941",
      "parents": [
        "55cf9cc5e003f6c63ddbad3cc55b87fd817b2e32"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 18:55:18 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 18:55:18 2026 +0800"
      },
      "message": "Refuse an expired signing key, and keep the release packages to what was voted\n\ncandidate checked that the signing key was in KEYS, but not that gpg still\ncounted it valid there. An expired or revoked key passed, and the vote mail\nsaid the key was in KEYS while every voter saw gpg warn about it. It now\nrefuses an expired or revoked primary key or signing subkey, and requires a\ngood signature that gpg does not report as expired or revoked.\n\nmake release now refuses ignored files that would reach a binary without\nreaching the source package, and compiled files that file(1) cannot tell\nfrom data. The checksum step stops at its first failure. The renderer check\npasses on a source package, which leaves out the two fonts. The binary\nLICENSE names only the modules the two binaries link.\n"
    },
    {
      "commit": "55cf9cc5e003f6c63ddbad3cc55b87fd817b2e32",
      "tree": "8c791bf3c1e49a3991734f02dc8193824ea291d2",
      "parents": [
        "df5c9c7b582e7a1e3c9f48f6c66e952cf511dedd"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 15:42:06 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 15:42:06 2026 +0800"
      },
      "message": "Bring the Setup pages and the README in line with the release work\n\nThe plugin page said its Windows hook command line had never run. CI now\nruns the packaged plugin on Windows x64 and ARM 64, and the page says what\nthat covers and what it does not. A Windows reader without Docker is sent\nto the Install page. The container page and the README say which tags a\nrun started by hand moves, and that binary packages are signed from 0.3.0\non.\n"
    },
    {
      "commit": "df5c9c7b582e7a1e3c9f48f6c66e952cf511dedd",
      "tree": "1ca52cb8809aa1971dd58958d14d858bf236c230",
      "parents": [
        "79488f02548140eaec454ecaf94d5a16e518ae1b"
      ],
      "author": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 15:42:06 2026 +0800"
      },
      "committer": {
        "name": "Wu Sheng",
        "email": "wu.sheng@foxmail.com",
        "time": "Fri Sep 11 15:42:06 2026 +0800"
      },
      "message": "Keep the changelog in SkyWalking\u0027s layout, and build the release text at release time\n\nEach version had two pages: its changelog, and a release-notes copy of it\nwith a download section. A root CHANGES.md indexed both beside the docs\nmenu. Apache SkyWalking and SkyWalking SWCK keep one page per version:\ndocs/en/changes/changes.md is the version in development, and\nchanges-X.Y.Z.md are released. asz now does the same.\n\nprepare tags a commit that keeps changes.md with its note removed, so the\ntag\u0027s menu and welcome page link a page the tag holds. The next commit\nmoves it to changes-X.Y.Z.md and opens a new changes.md. complete builds\nthe GitHub release text from the tag\u0027s changelog when it runs, so nothing\nis stored for it. The note is removed line by line, so a heading written\nright under it stays in the changelog.\n"
    }
  ],
  "next": "79488f02548140eaec454ecaf94d5a16e518ae1b"
}
