🏡


  1. September 16, 2026
    1. 🔗 facebookresearch/faiss v1.15.1 release

      faiss 1.15.1

    2. 🔗 gildas-lormeau/single-file-cli SingleFile CLI 2.14.0 release

      SingleFile CLI 2.14.0

      New features

      • --filename-replaced-character accepts a JSON array, for example '[" ", "_"]' to replace spaces with underscores. It is the way to ask for a replacement the space-separated form cannot express, a space as the replaced character being the one that was wanted

      Changes

      • Resources fetched outside the browser now carry a Referer. That lane runs when the browser could not get a resource itself, and it sent no referrer at all, so a host that varies its answer on one — a font service answering 404, an image host serving a placeholder — gave the saved page something the reader never saw. The value follows the referrer policy the document declares, the way the browser computes it
      • Runs of control characters in a filename collapse into a single replacement character, as they already did in the extensions and in single-file-core
      • --filename-replaced-character keeps the order it is given. An entry written without a replacement used to be moved to the end of the table
      • --help prints default values in the form the option reads back. The list of replaced characters was printed as a JSON array, so its control-character entry appeared with the backslashes doubled, and passing that back selected digits and uppercase letters instead of control characters
      • --load-deferred-content-dispatch-scroll-event also defaults to on when the CLI is used as a library, as it has on the command line since 2.13.0. The two now share one set of defaults and can no longer disagree

      Fixes

      • --filename-replaced-character no longer drops a replacement containing a space. The value was split on every space and only the first two parts were kept, so "> _G T_" replaced > with _G
      • --filename-replaced-character no longer accepts a value with nothing before the space. It matched no character at all, so the option did nothing without saying so; it is now reported as an invalid value
      • An entry given without a replacement no longer shifts the entries after it, which gave the following character someone else's replacement
      • --max-appended-data-length shows the real default in --help instead of a copy of it

      Fixes from single-file-core 1.6.2, 1.6.3 and 1.6.4

      • Fonts declared with font-style: oblique and an angle are no longer removed as unused when the page uses them as italic. The face was kept only when the style text matched exactly, so oblique 14deg never matched the italic the page asked for and the text lost its font. Font styles are now compared as angle ranges, the way the CSS specification defines the match
      • A filename replacement left empty in the middle of the table no longer writes undefined into the name

      Co-authored by Claude (Claude Code)

    3. 🔗 persiyanov/herdr-reviewr herdr-reviewr 0.38.0 release

      Changed

      • PR comments paint as comments. Badge images become P1/P2/P3 chips, mermaid fences become
        a ⧉ mermaid placeholder, HTML headings match markdown headings, <details> starts collapsed,
        and HTML tags no longer leak as source. The same renderer drives the All-files preview.

      • A review thread shows every reply in the read pane. GitHub, GitLab, and Azure DevOps.
        The ↳ N replies — open on GitHub line is gone. Click a <details> summary to toggle it;
        on the PR tab / expand/collapse every details in the open thread.

      • A capped list says so in the pane (newest 100 comments / newest 100 checks) instead of
        sending you to the forge.

    4. 🔗 HexRaysSA/plugin-repository commits sync repo: +2 releases, -1 release rss
      sync repo: +2 releases, -1 release
      
      ## New releases
      - [ida-nexus](https://github.com/hexrayssa/ida-nexus): 0.11.2
      - [mcrit-ida](https://github.com/danielplohmann/mcrit-plugin): 1.1.10
      
      ## Changes
      - [ida-codemode](https://github.com/hexrayssa/ida-codemode):
        - removed version(s): 0.4.0
      
    5. 🔗 danielplohmann/mcrit-plugin v1.1.10 release

      Fixed

      • Function Scope returning no matches for every function after the first, on SMDA 4.8 and later.
        SmdaReport.getFunctions() caches its result there, and the plugin reused a single outline
        report across queries while only swapping its xcfg, so every query after the first
        re-submitted the first function. A fresh outline is now built per query.

      • The outline now follows a replaced local report, so an upload after renaming no longer carries
        the previous report's metadata.

      What's Changed

      Changes

      • docs: add DeepWiki badge by @r0ny123 in #14
      • Pick the versioned libpython for idapyswitch by @danielplohmann in #19
      • Build a fresh SMDA report outline per function query (1.1.10) by @danielplohmann in #18
      • Release on a tag from the changelog, with the checks that were memory before by @r0ny123 in #15

      Full Changelog : v1.1.9...v1.1.10

  2. September 15, 2026
    1. 🔗 r/LocalLLaMA Don’t buy a $9K RTX 5090.... instead. rss
    2. 🔗 Quarkslab's blog Overview of Passive Optical Networks (PONs) Security rss

      You're watching a video on your favourite streaming platform. The platform sends video frames over HTTPS (encrypted), encapsulated in an IP packet with your IP address, ultimately forwarded to your fibre router inside an Ethernet frame. What you might not expect is that this Ethernet frame, containing your encrypted video, is actually received by all your neighbours' routers as well.

      The technology behind this behaviour is Passive Optical Networks (PON) : the optical signal received by your router is the same signal received by your neighbours, containing data for several nearby subscribers. It is effectively like having access to your neighbours' Ethernet cables from inside your home, but in one direction only.

      What can be extracted then depends entirely on what the subscriber is doing. If protocols are encrypted, the ONU only receives unencrypted metadata (source/destination addresses, ports, etc.), which can still reveal internet habits. If protocols are unencrypted, such as unencrypted DNS, unencrypted VoIP traffic, or legacy protocols, the entire payload is exposed.

      Several mechanisms can be activated to encrypt and authenticate each part of the signal so that only the intended recipient can decrypt and process it.

      ℹ️ Glossary

      There are many acronyms and definitions used in this article. The glossary is available at Annex A: Glossary.

      Fibre networks

      Internet Service Providers (ISPs) and companies often use Passive Optical Networks (PONs) to connect end-user devices to their infrastructure using optical fibre in the last kilometre (Fibre-to-the-x (FTTx)). If you have fibre internet in France or Europe, your router is probably using this technology, Gigabit-capable PON (GPON) or 10-Gigabit-capable (Symmetric) PON (XG(S)-PON) in particular.

      Before examining the security aspects of PONs, we will first review the evolution of the specifications, how PONs work, and how data is structured on the link.

      PON specifications

      Over the years, several iterations of PON specifications have been published by the ITU-T (International Telecommunication Union Telecommunication Standardization Sector):


      PON specifications timeline based on first specification publication date

      All the iterations in the timeline above are currently in use (although XGS- PON might be preferred to XG-PON) and can often coexist. While GPON has been around for more than 20 years, it is still standard for basic internet plans in France.

      As for the latest specification, 50-Gigabit-capable PON (50G-PON), released 5 years ago, some ISPs are only now starting to make real-world deployments. Orange, a French multinational ISP, made a small-scale demonstration of 50G-PON in October 2025 which it calls "the future standard for transmission systems in fibre access networks (FTTH)"1.

      Newer PON specifications can be grouped by the PON development phase they belong to:

      • Next-generation PON 1 (NG-PON1): XG-PON, XGS-PON.
      • Next-generation PON 2 (NG-PON2): NG-PON2.
      • Higher Speed PON (HSP): 50G-PON.
      • Very High Speed PON (VHSP): in development.

      We will stop at HSP/50G-PON, as it is the latest PON specification that has been released.

      Competing standards from the IEEE exist as well, to cite a few:

      • Ethernet PON (EPON): 1 Gbps symmetric.
      • Asymmetric 10G-EPON: 10 Gbps downstream, 1 Gbps upstream.
      • Symmetric 10G-EPON: 10 Gbps symmetric.

      We will focus on the ITU-T standards as they seem to be prevalent in France and Europe.

      Passive Optical Network (PON)

      ONUs, OLTs and the ODN

      PONs are optical fibre networks connecting Optical Line Terminations (OLTs) to Optical Network Units (ONUs) through Optical Distribution Networks (ODNs).

      ODNs used to be built around passive optical filters, splitters and combiners; hence the "Passive" Optical Network designation. However, newer specifications have introduced active elements such as Range Extenders, so the passive part depends on how the ODN is deployed.

      The ONU is the component of the router interfacing with the PON and it is sometimes a separate device connected to the router. When a single subscriber uses an ONU, it may also be called an Optical Network Termination (ONT).

      A simple description of an ONU would be that it is a device extracting Ethernet frames from the optical signal and forwarding them to the router. However, ONUs support other protocols as well (such as VoIP), have Quality-of- Service features (for time-sensitive signals), Layer-2/Layer-3 features (VLANs, IP configuration, TCP/UDP services, etc), configuration of xDSL if the ONU has xDSL ports, configuration of the security features, etc. The ONU Management and Control Interface (OMCI) specification, which defines the protocol used to configure the ONU, is 700 pages long. ONUs are more complex than they seem.

      ONUs are connected to an OLT which can be described as a fibre switch/router, through the ODN. The OLT is located on the service provider's side (ISP for instance).


      ONU (top) connected to the OLT (bottom) through an optical fibre. The ONU is plugged on one of the passive optical splitter ports, the splitter is integrated with the OLT here.

      The Optical Distribution Network: multiplexing optical signals

      One key aspect of an Optical Distribution Network (ODN) is that the signal is carried over a reduced number of optical fibres up to a splitter where individual fibres will carry the signal to the end user.

      To multiplex signals over a single fibre, several techniques are used depending on the specification or implementer's choice: Wavelength Division Multiplexing (WDM) , Time Division Multiplexing (TDM) or combining both using Time and Wavelength Division Multiplexing (TWDM).

      WDM uses different light frequencies to carry multiple signals. Due to carefully chosen frequencies, GPON, XG(S)-PON, NG-PON2 and 50G-PON can coexist on the ODN using a coexistence element (see ITU-T G.980519). For NG-PON2 and HSP, WDM is also used to increase the overall bandwidth by creating several channels that each use a different frequency pair. WDM can also designate the separation of transmit/receive frequencies to use on a single fibre, as opposed to using an individual fibre for each direction.

      Taking GPON as an example, the downstream signal is carried on a single fibre from the OLT up to a passive splitter. WDM is used: the upstream and downstream signals use separate wavelengths. The signal is then split (copied) between up to 128 users in the last kilometre and carried using individual fibres. Instead of running 128 individual fibres over up to 60km, only the last few metres will require individual fibres, which reduces the cost and complexity. However, this also means that all 128 users on the same ODN will receive the same downstream signal containing all users' data.


      Downstream PON

      As for the upstream signal, it uses Time Division Multiple Access (TDMA) , a specific type of TDM : each ONU sends bursts on a timing provided by the OLT, which enables all the individual optical signals from the ONUs to be passively aggregated again before reaching the OLT.


      Upstream PON

      For HSP and NG-PON2, the combination of WDM and TDM to create channels is designated as TWDM.

      The impact of PON architecture on security

      As the downstream signal is passively split between all the ONUs, they all receive the same downstream signal which contains their data but also their neighbours'. Basically, all downstream communications are broadcast at the link level, which mandates specific security measures.

      As for the upstream signal, it is not carried to other users. However, it can still be intercepted on each individual fibre or when aggregated by the combiner en route to the OLT.

      In both cases, authentication and encryption are desirable to avoid eavesdropping, tampering, or man-in-the-middle attacks, which brings us to the threat model of PON specifications.

      The PON's protocol stack

      In the previous sections, we learned how PONs are structured. This section presents the protocols used to configure the PON link and how data is framed. The objective is to present a condensed view of the frame structure; it can be glossed over and used as a reference for the next security-focused part.

      Three main configuration channels: Embedded OAM , PLOAM and OMCI

      Before going into the frame structures, let us mention three protocols used by GPON, XG(S)-PON, NG-PON2 and 50G-PON:

      • Embedded layer Operation, Administration and Maintenance (Embedded OAM): fields embedded in the frame headers for low latency access, for instance, the frame identifier or the upstream bandwidth allocation.
      • Physical layer Operation, Administration and Maintenance (PLOAM): used for other information not sent by the above that relates to the link layer, information that is less time-sensitive but used to configure the connection. For instance, sending the serial number to the OLT, the ONU ID, sending the data encryption key, etc.
      • ONU Management and Control Interface (OMCI): the protocol used for high-level configuration of the ONU (the channel is actually called ONU Management and Control Channel (OMCC)). The protocol is defined in the ITU-T G.988 ONU management and control interface (OMCI) specification 15. It uses a Management Information Base (MIB) synchronised between the ONU and the OLT, and allows complex mutual authentication, Layer 2 and Layer 3 configuration, ONU debug, etc.

      Four layers

      GPON, XG(S)-PON, NG-PON2 and 50G-PON all have similar frame structures, sharing a few similar characteristics:

      • When TDM is used (which is most of the time), the upstream frame structure is slightly different from the downstream frame structure.
      • The frames have four layers: Physical Medium Dependent (PMD) layer, Transmission Convergence (TC) PHY Adaptation Sublayer, TC Framing Sublayer (FS) and TC Service Adaptation Sublayer.
      • The lower layer is the PMD layer, which defines how data is encoded on the optical line.
      • The layer between the PMD and user functionalities (Service Data Units, SDUs) is the TC layer. It's called GTC for GPON4, XGTC for XG-PON9, XGS-PON TC10 for XGS-PON, TWDM TC for NG-PON214 and ComTC (for Common TC) for 50G-PON (HSP)17. 50G-PON/Higher Speed PON generalises the TC layer from XG-PON onwards.
      • The TC service adaptation sublayer contains (X)GEM frames ((10) Gigabit PON Encapsulation Method) which encapsulate the actual data payload (Ethernet frames for instance).

      The diagram below represents a downstream frame and its separation into different layers. This representation deliberately shows only a few fields which are relevant to explain how PON works or are security-related. Technically, it only applies to XG(S)-PON, NG-PON2 and 50G-PON, but GPON is similar. Moreover, while the name and overall purpose of a field might be the same between PON versions, the structure can differ.


      XG(S)-PON, NG-PON2 and 50G-PON downstream frame structure

      The fields shown on the diagram have the following use:

      • Superframe counter (SFC) : 51-bit (XG(S)-PON, NG-PON2 and 50G-PON), 30-bit (GPON), wraps around to 0. It is used in the data encryption process for the counter mode of operation.
      • PLOAMd : used by the OLT in the PLOAM configuration protocol, for instance to request a new data encryption key.
      • BWmap : not directly security-related, but contains the timings on which upstream frames can be sent for each ONU (for TDMA). More precisely, the OLT assigns Allocation IDs (Alloc-IDs) to the ONUs (a default one is always assigned for OMCI). These Alloc-IDs are then associated with Transmission Containers (T-CONs) which represent a group of logical connections which can be assigned a bandwidth. Finally, each downstream frame contains the bandwidth assignment for the Alloc-IDs, which means each Alloc-ID/T-CON will have a dedicated time to send an upstream burst. Note that the TC layer is only concerned with Alloc-IDs, T-CONs are managed through OMCI and another structure could map to Alloc-IDs. T-CONs are then mapped to (X)GEM ports, the smallest connection entity. Grouping (X)GEM ports into T-CON enables managing bandwidth for QoS (Quality-of-Service) purposes, to prioritise real-time signals such as voice audio.
      • Encryption key index : only for XG(S)-PON, NG-PON2 and 50G-PON. Indicates if the downstream payload is: unencrypted, encrypted using key 1, encrypted using key 2 (key index relative to the key slot). GPON does not have such a field: the OLT does not select a key slot but sends a Superframe counter value to the ONU through PLOAMd at which the new key (to be sent) will be used.

      In GPON, the FS Header and PSBd are actually merged as a single header (named GTC Header or PCBd) with a similar purpose. Another difference is that while the PLOAMd , BWmap and SFC fields are present in all PON versions, the Encryption key index is only present in XG(S)-PON, NG-PON2 and 50G-PON.

      The diagram below represents an upstream frame for XG(S)-PON, NG-PON2 and 50G-PON; it differs slightly for GPON, but it is overall very similar:


      XG(S)-PON, NG-PON2 and 50G-PON upstream frame structure

      The upstream frames are sent in bursts. Each Alloc-ID is given an opportunity to send data according to the BWMap field sent in the downstream frame. Alloc-ID bandwidth allocations that belong to the same ONU and are consecutive (in time and thus in the BWMap) are called an allocation series. They start with an FS Header and end with an FS Trailer.

      Threat models of GPON, XG(S)-PON, NG-PON2 and 50G-PON

      All the ITU-T PON specifications referenced have a threat model, which has greatly improved from GPON to XG-PON. XGS-PON/NG-PON2/50G-PON share the XG-PON threat model for the most part.

      The GPON4 threat model can be summarised as such:

      • An attacker or user having reprogrammed an ONU, or having otherwise access to the incoming optical signal, can access all downstream communications including data from all the other OLT users.
      • Other threats such as eavesdropping on the upstream link are considered impractical because the attacker would have to tap into the fibre, sometimes in public places and in a noticeable way for the PON.

      The XG-PON specification9 as well as XGS- PON9 10, NG- PON214 and 50G-PON17 have gone back on this model, taking upstream threats into account as well:

      • An attacker or user having reprogrammed an ONU, or having otherwise access to the incoming optical signal, can access all downstream communications including data from all the other OLT users.
      • An attacker or user having reprogrammed an ONU can impersonate another ONU and forge packets.
      • An attacker could intercept or generate traffic at any point on the ODN (which sometimes spans 60 km) between the OLT and the ONU, impersonating the OLT or an ONU.
      • An attacker or user could use any of the scenarios above to record and replay packets transmitted on the PON, or modify the packets (bitflips for instance).

      Overall, the threat model has improved significantly since GPON, considering security issues in both directions (OLT to ONU and ONU to OLT), as well as eavesdropping anywhere between the OLT and ONUs. This translates into improved security features as well.

      Additionally, a study group with members from China Telecommunications Corporation, Verizon, Huawei Technologies Co., Ltd. and ZTE Corporation has published recommendations named "Practical aspects of PON security" starting from 2022. A first supplement has been published20, with the objective "to guide the security experts on operators' expectations about the security of their access networks and on the criteria of a valuable security proposal, as well as the reference to the operators on configuring their networks to achieve the best possible security performance.".

      The improved threat model in the supplement separates the threats into several categories:

      • Eavesdropping: downstream and upstream.
      • Theft of service: unauthorised access to the PON.
      • Denial of service.
      • Theft of data.
      • Impersonation.
      • Disruption of operations: disruption from a single ODN to a larger scale (entire ISP for instance).

      The supplement insists that "regardless of how strong the link-level encryption strength is, no end-to-end data security property can be derived from it". Indeed, PON specifications are only concerned with establishing a secure channel between an ONU and an OLT, at the link level. This channel is not established between an end user and a service provider directly.

      Overall, the supplement gives more context to the threat model and makes recommendations on key management and handling of ONU identifier duplication. The XG-PON ITU-T G.987.39, NG- PON2 ITU-T G.989.314 and 50G-PON ITU-T G.9804.217 specifications' latest amendments actually recommend using this supplement for best practices related to key management. As it is recommended by the specifications, it probably must be implemented.

      One threat that does not seem to be mentioned in any of the threat models is the exploitation of vulnerabilities in the OMCI Management Entities, which have a large attack surface. The implementation of the OMCI Management Entities functionalities is down to the manufacturer so this might be outside the specification perimeter however.

      PON security measures

      Authentication and cryptographic schemes

      Authentication in ITU-T PONs can be surprisingly complex, as there are multiple identifiers and schemes available. They are sometimes combined, and the authentication methods do not all ensure the same level of security.

      We will separate the use of these identifiers into two categories:

      • Identification: the OLT requests an identifier, but there is no secure channel bound to this identifier. In GPON, the OLT can request an identifier and then request an encryption key, but the key is not bound to the identifier, which is why only one authentication scheme is considered for GPON (OMCI-based).
      • Authentication: the OLT (and the ONU if the authentication is symmetric) uses a cryptographic scheme to authenticate its counterpart. The results of this authentication can be used to establish a secure channel between the two devices. Keep in mind that, as we'll see in the next sections, a successful authentication does not mean the secure channel is activated, only that it can be.

      All PON versions start with an identification step: at the very start of its connection to the OLT, the ONU sends its serial number.

      Then, for GPON, cryptographic authentication is entirely optional. For XG-PON and later, a Registration ID -based authentication will always be performed after ONU initialis ation, whether Registration_ID is the default null string or a provisioned one. If a Registration_ID is not provisioned, it will not provide meaningful authentication.

      After the mandatory identification or authentication steps, the OLT can start a new authentication process at any point, or request other identifiers. For authentication, up to three cryptographic schemes are available depending on the PON version. Some of these schemes allow for mutual authentication (ONU to OLT, but also OLT to ONU), all of them lead to the creation of a Master Session Key (MSK) which can then be used to establish a secure channel.


      Authentication decision diagram

      The Logical ONU ID (LOID) / LOID password identifiers often present in ONU web interfaces can be a bit confusing. They seem to be both implemented in custom OMCI Management Entities (as is the case for the Management Entity 65530 used by Huawei ONUs) and in the standard "ONU-G" OMCI Management Entity. The O3 Labs website21 lists several vendor-specific OMCI Management Entities.

      Note that a combination of these identifiers and schemes can be used at different steps during the configuration of the ONU. For an XGS-PON link the following sequence could be used:

      1. Identify the ONU through the serial number sent at the start of the link initialisation.
      2. Request the Registration ID from the ONU, derive an MSK using the corresponding cryptographic scheme and enable encryption.
      3. Start the OMCI cryptographic scheme and transition to the new MSK.

      The (mutual) secure authentication mechanisms

      In the previous section, we mentioned three authentication mechanisms based on cryptography: Registration ID-based, OMCI-based and IEEE 802.1X-based.

      Only the last two are mutual authentication schemes that authenticate both the ONU and the OLT.

      Registration ID-based authentication

      Only available in XG(S)-PON, NG-PON2 and 50G-PON. This is the default authentication mode based on a 36-byte value called Registration_ID and it is not mutual. The Registration_ID is requested by the OLT during the ONU activation process and can be queried at any time through a PLOAM message, which is unencrypted.

      This mechanism is thus vulnerable to several threats, for instance:

      • An attacker can unplug the target optical fibre, plug it in their OLT, request the Registration_ID , derive the keys and use them to decrypt the encrypted data encryption key transmitted through PLOAM after the device has been plugged back to the original OLT.
      • An attacker could tap at any point between the targeted user and the OLT and see the Registration_ID in clear form, derive the keys and use them to decrypt the encrypted data encryption key transmitted through PLOAM.

      The Registration_ID is not meant to be changed regularly, the attacker can query it once and reuse it at any time. Moreover, if it is unchanged, the default is used.

      A key consideration is that Registration_ID \def\pelican{\textrm{pelican}^2} \text{Registration\ID} has a default value (0x00)36 \def\pelican{\textrm{pelican}^2} (0x00){36}, which will be used for the Registration ID-based authentication unless a Registration_ID has been provisioned. Thus, the specifications effectively allow the use of one of the cryptographic authentication schemes with a known (weak) secret. Having the possibility to use an unauthenticated/unencrypted channel, which would not provide a false sense of security, would seem preferable.

      Registration ID-based MSK derivation

      Let:

      • Registration_ID \def\pelican{\textrm{pelican}^2} \text{Registration\_ID} be the 36-byte Registration ID.
      • TMSK \def\pelican{\textrm{pelican}^2} T_{MSK} be the required MSK length.
      • K \def\pelican{\textrm{pelican}^2} K be the 128-bit AES key used in the AES-ECMAC definition.
      • M \def\pelican{\textrm{pelican}^2} M be the input message used in the AES-ECMAC definition.
      • Tlen \def\pelican{\textrm{pelican}^2} Tlen be the requested MAC output length in bits.
      • MlessLastBlock \def\pelican{\textrm{pelican}^2} M_{\text{lessLastBlock}} be the M \def\pelican{\textrm{pelican}^2} M with its final 16 octets removed.

      MSK \def\pelican{\textrm{pelican}^2} MSK is derived the following way:

      MSK={AES-128-CMAC&af((0x55)16,Registration_ID,128),TMSK=128AES-128-ECMAC&af((0x55)16,Registration_ID,256),TMSK=256 (50G-PON/HSP) \def\pelican{\textrm{pelican}^2} MSK = \begin{cases} \operatorname{AES-128-CMAC}\left((\text{0x55}){16}, \text{Registration\_ID}, 128\right), & T{MSK}=128 \\[6pt] \operatorname{AES-128\text{-}ECMAC}\left((\text{0x55}){16}, \text{Registration\_ID}, 256\right), & T{MSK}=256 \text{ (50G-PON/HSP)} \end{cases}

      where

      AES-128-ECMAC&af(K,M,Tlen)={AES-128-CMAC&af(K,M,Tlen),Tlen≤128AES-128-CMAC&af(K,MlessLastBlock,128)&smidAES-128-CMAC&af(K,M,128),Tlen=256 \def\pelican{\textrm{pelican}^2} \operatorname{AES-128\text{-}ECMAC}(K, M, Tlen) = \begin{cases} \operatorname{AES-128-CMAC}(K, M, Tlen), & Tlen \leq 128 \\[4pt] \operatorname{AES-128-CMAC}\left(K, M_{\text{lessLastBlock}}, 128\right) | \operatorname{AES-128-CMAC}(K, M, 128), & Tlen = 256 \end{cases}

      OMCI-based authentication

      This is one of the two mutual secure authentication mechanisms, now defined in ITU-T G.98815 (originated in ITU-T G.984.45 for GPON). The scheme assumes a Pre-Shared Key (PSK \def\pelican{\textrm{pelican}^2} PSK) has been provisioned on both the ONU and OLT beforehand.

      A three-step challenge-based mutual authentication is used:

      Three-step OMCI authentication

      1. The OLT sends its list of supported MAC (Message Authentication Code) functions and OLT random challenge \def\pelican{\textrm{pelican}^2} OLT\ random\ challenge to the ONU.
      2. Then, the ONU selects one of the supported OLT MAC functions, then sends the ONU random challenge \def\pelican{\textrm{pelican}^2} ONU\ random\ challenge and the following MAC to the OLT: MACFunction(PSK,(selected_cryptographic_capabilities&smidOLT random challenge&smidONU random challenge&smid(0x00)16)) \def\pelican{\textrm{pelican}^2} \text{MACFunction}(\text{PSK}, (selected\cryptographic\_capabilities|OLT\ random\ challenge|ONU\ random\ challenge|(0x00){16}))
      3. Finally, OLT then sends a MAC to the ONU as well: MACFunction(PSK,(selected_cryptographic_capabilities&smidONU random challenge&smidOLT random challenge&smidONU Serial Number)) \def\pelican{\textrm{pelican}^2} \text{MACFunction}(\text{PSK}, (selected\_cryptographic\_capabilities|ONU\ random\ challenge|OLT\ random\ challenge|ONU\ Serial\ Number))

      The OLT advertises its supported MACFunction \def\pelican{\textrm{pelican}^2} MACFunction in the "OLT crypto capabilities" attribute of the OMCI "Enhanced security control" Management Entity. Then, the ONU chooses one of these MACFunctions \def\pelican{\textrm{pelican}^2} MACFunctions and sets the "ONU selected crypto capabilities" attribute of the OMCI "Enhanced security control" Management Entity. It is then used as onu_selected_cryptographic_capabilities \def\pelican{\textrm{pelican}^2} \text{onu\_selected\_cryptographic\_capabilities}.

      MACFunction \def\pelican{\textrm{pelican}^2} MACFunction is chosen from the following list:

      • AES-CMAC-128 (default, must be supported).
      • HMAC-SHA-256.
      • HMAC-SHA-512.

      Master Session Key derivation

      Finally, the MSK \def\pelican{\textrm{pelican}^2} MSK is derived from the PSK \def\pelican{\textrm{pelican}^2} PSK and the challenges. The following formula is used when a 128-bit MSK \def\pelican{\textrm{pelican}^2} MSK is required, which is always the case for GPON, XG(S)-PON and NG-PON2:

      OMCI-based 128-bit MSK \def\pelican{\textrm{pelican}^2} MSK derivation

      MSK=MACFunction(PSK,(OLT random challenge&smidONU random challenge)), take the 128 most significant bits. \def\pelican{\textrm{pelican}^2} \text{MSK} = \text{MACFunction}(\text{PSK}, (OLT\ random\ challenge | ONU\ random\ challenge))\text{, take the 128 most significant bits.}

      However, 50G-PON also supports 256-bit MSKs. When combining the ITU-T G.988 (OMCI)15 and ITU-T G.9804.2 (50G-PON)17 specifications, it is unclear how MSK \def\pelican{\textrm{pelican}^2} MSK should be derived in this situation. ITU-T G.9804.2 also defers to ITU-T G.987.39 for OMCI authentication.

      When MACFunction=AES-CMAC-128 \def\pelican{\textrm{pelican}^2} \text{MACFunction}=\text{AES-CMAC-128}, the equation is clear:

      OMCI-based 50G-PON 256-bit MSK derivation for MACFunction=AES-CMAC-128 \def\pelican{\textrm{pelican}^2} \text{MACFunction}=\text{AES-CMAC-128}

      MSK=AES-128-ECMAC&af(PSK,(OLT random challenge&smidONU random challenge),256) \def\pelican{\textrm{pelican}^2} \text{MSK} = \operatorname{AES-128\text{-}ECMAC}\left(\text{PSK}, (OLT\ random\ challenge | ONU\ random\ challenge), 256\right)

      where
      AES-128-ECMAC&af(K,M,Tlen)={AES-128-CMAC&af(K,M,Tlen),Tlen≤128AES-128-CMAC&af(K,MlessLastBlock,128)&smidAES-128-CMAC&af(K,M,128),Tlen=256 \def\pelican{\textrm{pelican}^2} \operatorname{AES-128\text{-}ECMAC}(K, M, Tlen) = \begin{cases} \operatorname{AES-128-CMAC}(K, M, Tlen), & Tlen \leq 128 \\[4pt] \operatorname{AES-128-CMAC}\left(K, M_{\text{lessLastBlock}}, 128\right) | \operatorname{AES-128-CMAC}(K, M, 128), & Tlen = 256 \end{cases}

      When MACFunction=HMAC-SHA-256 \def\pelican{\textrm{pelican}^2} \text{MACFunction}=\text{HMAC-SHA-256} or MACFunction=HMAC-SHA-512 \def\pelican{\textrm{pelican}^2} \text{MACFunction}=\text{HMAC-SHA-512} are used, the specifications imply one of two things:

      • Following ITU-T G.9804.2 Annex C "Secure mutual authentication", the output of MACFunction \def\pelican{\textrm{pelican}^2} \text{MACFunction} is truncated to 256 bits, as HMAC-SHA-256 \def\pelican{\textrm{pelican}^2} \text{HMAC-SHA-256} generates a 256-bit value and HMAC-SHA-512 \def\pelican{\textrm{pelican}^2} \text{HMAC-SHA-512} a 512-bit value.
      • ITU-T G.988 requires MSK \def\pelican{\textrm{pelican}^2} MSK to be truncated to 128 bits. According to ITU-T G.9804.2 clause 9.13, MSK \def\pelican{\textrm{pelican}^2} MSK must be extended to 256 bits using the same process as the "OMCI-based 50G-PON 256-bit MSK derivation for MACFunction=AES-CMAC-128 \def\pelican{\textrm{pelican}^2} \text{MACFunction}=\text{AES-CMAC-128}" equation above.

      The key contradictory quotes are available in Annex C: Contradictory specification quotes relative to the derivation of a 256-bit MSK \def\pelican{\textrm{pelican}^2} MSK for HMACs with an output size of 256-bit or more.

      As both the ONU and OLT must derive the same MSK \def\pelican{\textrm{pelican}^2} MSK, a manufacturer testing this specific case with different ONUs or OLTs would notice any implementation mismatch. A clarification of the specification would make clear what is actually implemented, but all manufacturers should have the same implementation eventually. As 50G-PON is not widely deployed, they might not have faced this choice yet, especially with these specific MACFunctions \def\pelican{\textrm{pelican}^2} \text{MACFunctions}.

      IEEE 802.1X-based authentication

      This is the second mutual secure authentication mechanism, only available in XG(S)-PON, NG-PON2 and 50G-PON. Its implementation is mostly detailed in Annex D of ITU-T G.987.39.

      IEEE 802.1X supports many authentication methods through the Extensible Authentication Protocol (EAP)23. EAP- GPSK24 which is based around a pre-shared key, must be supported according to ITU-T G.987.39. EAP- TLS25 is also mentioned in the specification and allows the use of public-key cryptography. Any EAP method that supports mutual authentication and the generation of a secret MSKEAP \def\pelican{\textrm{pelican}^2} MSK_{EAP} can be used.

      RFC 374823 mentions that the MSKEAP \def\pelican{\textrm{pelican}^2} MSK_{EAP} generated must be at least 64 bytes long, however we need only 16 bytes or 32 bytes (50G-PON only).

      IEEE 802.1X-based MSK derivation

      Let TMSK \def\pelican{\textrm{pelican}^2} T_{MSK} be the required MSKPON \def\pelican{\textrm{pelican}^2} MSK_{PON} size in bits. MSKPON \def\pelican{\textrm{pelican}^2} MSK_{PON} is derived from MSKEAP \def\pelican{\textrm{pelican}^2} MSK_{EAP} as follows:

      MSKPON=TMSK most significant bits of MSKEAP \def\pelican{\textrm{pelican}^2} MSK_{PON} = T_{MSK}\text{ most significant bits of } MSK_{EAP}

      A note on Forward Secrecy

      Only one authentication scheme, the IEEE 802.1X-based scheme with the right EAP (EAP-TLS with a Diffie-Hellman key exchange for instance), might lead to an encrypted session that provides perfect forward secrecy. For the other schemes, the leak of the Registration_ID or PSK would enable attackers to decrypt the signal in harvest now, decrypt later attacks. The encrypted data encryption key contained in the PLOAM messages could be decrypted, and then the payloads of the XGEM frames.

      Key Derivation

      MSK \def\pelican{\textrm{pelican}^2} MSK and its derived keys have three purposes: encrypting the data encryption key (using the MSK \def\pelican{\textrm{pelican}^2} MSK for GPON or the Key Encryption Key (KEK \def\pelican{\textrm{pelican}^2} KEK) otherwise), authenticating PLOAM messages and authenticating OMCI messages (XG-PON and above only).


      Key derivation decision diagram

      Note that the typos and wordplays in the strings match the actual values used by the specifications, as specific string lengths were required.

      XG(S)-PON and NG-PON2 derivations

      SK=AES-128-CMAC(MSK,(SN&smidPON-TAG&smid"SessionK")) \def\pelican{\textrm{pelican}^2} SK = \text{AES-128-CMAC}(MSK, (\text{SN}|\text{PON-TAG}|\text{"SessionK"})) OMCI_IK=AES-128-CMAC(SK,"OMCIIntegrityKey") \def\pelican{\textrm{pelican}^2} OMCI\_IK = \text{AES-128-CMAC}(SK, \text{"OMCIIntegrityKey"}) PLOAM_IK=AES-128-CMAC(SK,"PLOAMIntegrtyKey") \def\pelican{\textrm{pelican}^2} PLOAM\_IK = \text{AES-128-CMAC}(SK, \text{"PLOAMIntegrtyKey"}) KEK=AES-128-CMAC(SK,"KeyEncryptionKey") \def\pelican{\textrm{pelican}^2} KEK = \text{AES-128-CMAC}(SK, \text{"KeyEncryptionKey"})

      50G-PON derivations

      Let:

      • K \def\pelican{\textrm{pelican}^2} K be the key used in the BC-ECMAC definition.
      • M \def\pelican{\textrm{pelican}^2} M be the input message used in the BC-ECMAC definition.
      • Tlen \def\pelican{\textrm{pelican}^2} Tlen be the requested MAC output length in bits.
      • BC \def\pelican{\textrm{pelican}^2} BC be the chosen Block Cipher.
      • MlessLastBlock \def\pelican{\textrm{pelican}^2} M_{\text{lessLastBlock}} be M \def\pelican{\textrm{pelican}^2} M with its final 16 octets removed.

      SK=BC-ECMAC(MSK,(SN&smidPON-TAG&smidPON-TAG&smidSN),Tlen) \def\pelican{\textrm{pelican}^2} SK = \text{BC-ECMAC}(MSK, (SN|\text{PON-TAG}|\text{PON-TAG}|SN), Tlen) OMCI_IK=BC-ECMAC(SK, "OMCIIntegrityKeyMakeOMCImoreSafe",Tlen) \def\pelican{\textrm{pelican}^2} OMCI\IK = \text{BC-ECMAC}(SK, \text{ "OMCIIntegrityKeyMakeOMCImoreSafe"}, Tlen) PLOAM_IK=AES-128-CMAC(SK16 least significant bytes,"PLOAMIntegrtyKey") \def\pelican{\textrm{pelican}^2} PLOAM\_IK = \text{AES-128-CMAC}(SK\text{16 least significant bytes}, \text{"PLOAMIntegrtyKey"}) KEK=BC-ECMAC(SK,"KeyEncryptionKeyMakeKEKMoreSafey",Tlen) \def\pelican{\textrm{pelican}^2} KEK = \text{BC-ECMAC}(SK, \text{"KeyEncryptionKeyMakeKEKMoreSafey"}, Tlen) where BC-ECMAC&af(K,M,Tlen)={BC-CMAC&af(K,M,Tlen),Tlen≤128BC-CMAC&af(K,MlessLastBlock,128)&smidBC-CMAC&af(K,M,128),Tlen=256 \def\pelican{\textrm{pelican}^2} \operatorname{BC\text{-}ECMAC}(K, M, Tlen) = \begin{cases} \operatorname{BC-CMAC}(K, M, Tlen), & Tlen \leq 128 \\[4pt] \operatorname{BC-CMAC}\left(K, M_{\text{lessLastBlock}}, 128\right) | \operatorname{BC-CMAC}(K, M, 128), & Tlen = 256 \end{cases}

      For GPON, the data encryption key is only sent encrypted when the OMCI authentication is used; otherwise it is sent in clear form. The key is always sent encrypted in XG(S)-PON, NG-PON2 and 50G-PON, using the KEK \def\pelican{\textrm{pelican}^2} KEK key derived from the MSK.

      The other two keys derived from the MSK in XG(S)-PON, NG-PON2 and 50G-PON are used for the authentication of OMCI and PLOAM messages, so only OMCI and PLOAM have authenticity checks, not the main payload. GPON does not have such a mechanism.

      Authentication of PLOAM and OMCI messages

      Only XG(S)-PON, NG-PON2 and 50G-PON authenticate the PLOAM and OMCI messages; GPON does not. Authentication ensures the messages have been sent by the right peer and that they have not been tampered with.

      The PLOAM and OMCI Message Integrity Check (MIC) values are derived the following way:

      PLOAM and OMCI MIC calculation

      PLOAM-MIC=AES-128-CMAC(PLOAM_IK,(Cdir&smidPLOAM_CONTENT),64) \def\pelican{\textrm{pelican}^2} \text{PLOAM-MIC} = \text{AES-128-CMAC} (\text{PLOAM\IK}, (C{dir} | \text{PLOAM\_CONTENT}), 64)

      OMCI-MIC=BC-CMAC(OMCI_IK,(Cdir&smidOMCI_CONTENT),32) \def\pelican{\textrm{pelican}^2} \text{OMCI-MIC} = \text{BC-CMAC} (\text{OMCI\IK}, (C{dir} | \text{OMCI\CONTENT}), 32)
      where Cdir=0x01 \def\pelican{\textrm{pelican}^2} C
      {dir} = 0x01 for downstream and Cdir=0x02 \def\pelican{\textrm{pelican}^2} C_{dir} = 0x02 for upstream.

      and

      BC-CMAC \def\pelican{\textrm{pelican}^2} \text{BC-CMAC} is AES-128-CMAC \def\pelican{\textrm{pelican}^2} \text{AES-128-CMAC} for XG(S)-PON and NG- PON2. For 50G-PON, the chosen block cipher used across data encryption, key derivation and message integrity checks is used for OMCI. This allows 50G-PON to use stronger keys (256-bit). PLOAM, however, always uses AES-128-CMAC \def\pelican{\textrm{pelican}^2} \text{AES-128-CMAC}.

      PLOAM and OMCI authentication is always enabled; however, before any MSK has been derived a default key is used for PLOAM: 0x5516 \def\pelican{\textrm{pelican}^2} 0x55_{16}. The default key is always used for downstream broadcast PLOAM messages, as well as some unicast PLOAM messages such as the Serial_Number_ONU , Deactivate_ONU-ID , Request_Registration and Registration PLOAM messages. After that, the keys derived from the MSK established from the Registration_ID are used, and finally the keys derived from any new MSK established from one of the authentication mechanisms.

      Generating the data encryption keys

      There are two types of data encryption keys: unicast and multicast (broadcast). In the previous sections, we only mentioned which keys are used to encrypt the unicast data encryption key; not how it is generated.

      The unicast data encryption key used in the GPON, XG(S)-PON and NG-PON2 specifications is generated by each ONU, not the OLT.

      GPON, XG-PON and NG-PON2 data encryption key generation

      EncryptionKey=ONU-generated random generated value of size 128 bits \def\pelican{\textrm{pelican}^2} EncryptionKey = \text{ONU-generated random generated value of size 128 bits}

      A secure random number generator must be used.

      Moreover, if the key is not generated properly, all the authentication and key encryption will not matter as the attacker could bruteforce the data encryption key. Thus, according to the XG(S)-PON and NG-PON2 specifications, "the ONU generates a new encryption key using a random number generator suitable for cryptographic purposes"14. The GPON specification has a similar mention "The ONU should generate a cryptographically unpredictable key."4.

      As for 50G-PON, the unicast data encryption key is now derived from both an OLT and an ONU random number, instead of being taken directly from the random number generator output:

      50G-PON data encryption key generation

      Let:

      • KEK \def\pelican{\textrm{pelican}^2} \text{KEK} be the Key Encryption Key derived from MSK \def\pelican{\textrm{pelican}^2} MSK.
      • BC \def\pelican{\textrm{pelican}^2} \text{BC} be the chosen Block Cipher.
      • Tlen \def\pelican{\textrm{pelican}^2} \text{Tlen} be the BC \def\pelican{\textrm{pelican}^2} \text{BC} key size.
      • KeyControl_RandomX \def\pelican{\textrm{pelican}^2} \text{KeyControl\_RandomX} and KeyControl_RandomX \def\pelican{\textrm{pelican}^2} \text{KeyControl\_RandomX} be values sent to the ONU through PLOAM by the OLT.
      • BC-ECMAC \def\pelican{\textrm{pelican}^2} \text{BC-ECMAC} as previously defined.
      • BC-ECB \def\pelican{\textrm{pelican}^2} \text{BC-ECB} be BC \def\pelican{\textrm{pelican}^2} \text{BC} with the ECB cipher mode.

      EncryptionKey=BC-ECMAC(KEK,ONUKeyContrib&smidOLTKeyContrib,Tlen) \def\pelican{\textrm{pelican}^2} EncryptionKey = \text{BC-ECMAC}(KEK, \text{ONUKeyContrib}|\text{OLTKeyContrib}, Tlen)

      where

      OLTKeyContrib={BC-ECB&af(KEK,KeyControl_RandomX),Tlen=128BC- ECB&af(KEK,KeyControl_RandomX|KeyControl_RandomY),Tlen=256 \def\pelican{\textrm{pelican}^2} \text{OLTKeyContrib} = \begin{cases} \operatorname{BC-ECB}(KEK, \text{KeyControl\_RandomX}), & Tlen=128 \\[4pt] \operatorname{BC-ECB}(KEK, \text{KeyControl\_RandomX|KeyControl\_RandomY}), & Tlen=256 \end{cases}

      and

      ONUKeyContrib=ONU-generated random generated value of size Tlen \def\pelican{\textrm{pelican}^2} \text{ONUKeyContrib} = \text{ONU-generated random generated value of size Tlen}
      A secure random number generator must be used.

      However, the key is still generated on the ONU side, sent encrypted to the OLT and used as-is. Thus, it seems that the ONU can still hardcode a key or ignore the OLT's contribution, and is responsible for the key strength more generally. But the ONU can now use the OLT entropy pool to increase the data encryption key strength.

      Finally, for XG-PON and above, the data encryption key is sent encrypted to the OLT, using the chosen block cipher in ECB mode and KEK \def\pelican{\textrm{pelican}^2} KEK as key. For GPON, MSK \def\pelican{\textrm{pelican}^2} MSK is used instead of KEK \def\pelican{\textrm{pelican}^2} KEK, and encryption of the data encryption key not mandatory.

      Encryption of the data encryption key

      For GPON Encrypted EncryptionKey=AES-128-ECB&af(MSK,Encryption Key), \def\pelican{\textrm{pelican}^2} \text{Encrypted EncryptionKey} = \operatorname{AES-128-ECB}(MSK, \text{Encryption Key}),

      For XG-PON and above Encrypted EncryptionKey=BC-ECB&af(KEK,Encryption Key), \def\pelican{\textrm{pelican}^2} \text{Encrypted EncryptionKey} = \operatorname{BC-ECB}(KEK, \text{Encryption Key}),
      where BC=AES−128 \def\pelican{\textrm{pelican}^2} BC = AES-128 for XG(S)-PON and NG-PON2.

      XG(S)-PON, NG-PON2 and 50G-PON also allow the use of a multicast data encryption key which is generated on the OLT and allows a frame to be encrypted once so that it can be decrypted by multiple ONUs.

      Enabling encryption

      Encryption is mostly disabled by default for all PON versions.

      For GPON, the encryption of a GEM port is activated through PLOAM, and only the downstream frames are encrypted. For XG-PON and above, the default XGEM port which is used for OMCI is always set for bidirectional unicast encryption, other XGEM ports must be provisioned (unicast/multicast, direction of the encryption, etc). However, the encryption of the frame is still enabled by the Key_index field in the frame header, although the XG-PON specification does mention that it must be done "within the explicitly configured or pre-defined capabilities of the associated XGEM port".


      Enabling encryption decision diagram

      For the encryption of the OMCI messages (on the OMCC channel), no OMCI Management Entity needs to be configured. For XG-PON and above, ITU-T G.98815 mentions that "If the GEM frame itself contains an encryption key index, it is understood to refer to the unicast key and to require encryption both up- and downstream." and the XG(S)-PON and above specifications say "Whenever the default XGEM port traffic is encrypted in the downstream direction, the ONU is expected to encrypt the default XGEM port traffic upstream". One interpretation of that last quote could imply that an unencrypted frame can be sent to the default (OMCI) XGEM port, which slightly contradicts the ITU-T G.988 specification.

      Implementers should make sure that the Key_index value of a specific frame matches the configuration of the XGEM port.

      PLOAM messages and OMCI Management Entities relevant to security features

      There are a few PLOAM messages and OMCI Management Entities related to security features that are scattered in the specifications. Annex B: PLOAM and OMCI security-related messages can be used as a reference and might be useful for the next section.

      Encrypted communications

      Finally! We've identified the ONU, passed the authentication process, established an MSK , derived the keys, generated a random data encryption key and sent it encrypted to the OLT, we've enabled the encryption on the (X)GEM port, set the right fields in the (X)GEM header, authenticated the OMCI and PLOAM messages (unless GPON), and at last we can send our encrypted message!

      Block cipher and CTR mode

      First of all, only the payload of the X(GEM) frame is encrypted, not the X(GEM) header.

      The default encryption method is AES-128-CTR (AES-128 with the counter mode of operation specified in NIST SP 800-38A22) and it must be supported by both the ONU and the OLT. In addition to AES-128, 50G-PON (ITU-T G.9804.217) must also support AES-256. However, for 50G-PON, the OMCI "ONU2-G" Management Entity can advertise and enable support for other block ciphers: Camellia-128, Camellia-256 and SM4-128.

      While technically, the NIST SP 800-38A22 "assumes that a FIPS-approved symmetric key block cipher algorithm has been chosen as the underlying algorithm", and Camellia and SM4 are not NIST- approved, the same counter mode is used for all block ciphers.

      Even though CTR is a strong encryption mode, it does not provide authentication, which means an attacker can blindly modify the encrypted payload. Other protocols such as TLS 1.3 use AEAD (Authenticated encryption with additional data) modes to provide both encryption and authentication of the main payload, as well as authentication of additional data that must stay visible (a header for instance).

      The Counter

      The counter is reset at the start of each (X)GEM frame, and its initial value is called Initial Counter Block in XG-PON and later but this designation works for GPON as well. It is then increased by one after each 16-byte block inside the (X)GEM frame.

      The Initial Counter Block is a function of the SFC which is sent by the OLT and the Intraframe counter (IFC). The IFC is reset to 0 at the start of each FS frame, increased every 4 bytes for GPON and every 16 bytes for XG-PON and later. The IFC value used in the Initial Counter Block calculation is the IFC value at the first byte of the GEM header for GPON, and at the position of the first four bytes for XG-PON and later (XGEM frames are aligned on a 4-byte boundary).

      The figures below represent the SFC , IFC and how (X)GEM frames are encrypted for downstream GPON and XG-PON (and above).


      Encryption of a downstream (GPON) GEM frame


      Encryption of a downstream XGEM frame

      Duplicated counter blocks

      The XG-PON specification and above mention two SFC values which lead to duplicated counter blocks in both directions:

      • For XG(S)-PON and NG-PON2: SFC(49..0)=0b1(0)49 \def\pelican{\textrm{pelican}^2} SFC(49..0)=0b1(0){49} and SFC(49..0)=0b0(1)49 \def\pelican{\textrm{pelican}^2} SFC(49..0)=0b0(1){49}. According to the specifications, the duplicated counter blocks appear for 250μs once every 4000 years. SFC(49..0) \def\pelican{\textrm{pelican}^2} SFC(49..0) is used, as one of the 51 bits of SFC is dropped before the counter calculation.
      • For HSP/50G-PON: SFC(47..0)=0b1(0)47 \def\pelican{\textrm{pelican}^2} SFC(47..0)=0b1(0){47} and SFC(47..0)=0b0(1)47 \def\pelican{\textrm{pelican}^2} SFC(47..0)=0b0(1){47}. According to the specification, the duplicated counter blocks appear for 100μs once every 1000 years. SFC(47..0) \def\pelican{\textrm{pelican}^2} SFC(47..0) is used, as three of the 51 bits of SFC are dropped before the counter calculation.

      where SFC(x..y) \def\pelican{\textrm{pelican}^2} SFC(x..y) represents SFC \def\pelican{\textrm{pelican}^2} SFC's bits x to y (included) in that order.

      To mitigate this issue, the specifications recommend initialising SFC to a small value.

      As for the upstream payloads, GPON does not encrypt them. For XG-PON and above, upstream frames can be encrypted, when they are the operation is very similar to the encryption of downstream frames.

      The main difference is how IFC is calculated: instead of starting at 0 \def\pelican{\textrm{pelican}^2} 0, IFC starts with a value derived from the StartTime at which the upstream burst is sent. This way, IFC is unique for each Alloc-ID.

      Reduced data encryption strength: why?

      All the ITU-T PON specifications discussed specify the use of data encryption keys with "reduced effective length".

      Instead of generating a 128-bit encryption key, an ONU may generate a key of size Leff \def\pelican{\textrm{pelican}^2} L_{eff} bits where Leff \def\pelican{\textrm{pelican}^2} L_{eff} is a multiple of 8, filling the remaining most significant bytes with 0x55 \def\pelican{\textrm{pelican}^2} 0x55.

      Leff \def\pelican{\textrm{pelican}^2} L_{eff} can be reported through the OMCI "Enhanced security control" Management Entity and the "Effective key length" attribute, but it is optional.

      While this provision was probably in place to support older devices, it is odd that it is still present in newer specifications that require more capable devices.

      Bringing it all together: ONU initialisation and key switching

      While we have all the bricks to authenticate and encrypt secure channels between ONUs and OLTs, timing is important: some authentication methods are not available right away, keys are renewed regularly (key switching) or must transition from one authentication method to another, etc.

      The diagram below presents the key events of an XG-PON ONU from power-up to the establishment of a secure data channel, highlighting the transitions between authentication schemes. Note that this is a "happy path" scenario; it is designed to demonstrate the integration of the security mechanisms discussed in this post rather than provide an exhaustive state-machine analysis of the full specification.

      On the left of the diagram are represented the approximate ONU and key management states that are used in state-machines in the specification; not all events are displayed and some are simplified.


      XG-PON timeline for power-up to secure channel

      The data encryption key should be regularly regenerated by the ONU on the OLT's request; however, the renewal frequency is at the OLT's discretion.

      During key renewal, all PON specifications implement measures to ensure only valid keys are used and the link is not left broken. For GPON, the OLT simply sends a superframe counter value to the ONU, at which the new key will be used. For XG-PON and above, the process is more complex and involves timers and state machines. While the XG-PON and later specifications mention two ONU timers (the "ONU key exchange waiting timer" and "Key Ack waiting timer"), the ITU-T Supplement 8120 deprecates them "the ONT-side timers are deprecated", leaving timeout management to the OLT.

      Conclusion

      ITU-T PON specifications implement link-level encryption and authentication between OLTs and ONUs to protect against eavesdropping, data theft, and impersonation. However, there are many choices to make and steps to follow, and the specifications do not always have a "secure by default" approach.

      GPON, as the oldest PON specification in this blog post, offers the most limited security: downstream-only encryption and optional OMCI-based authentication. Without OMCI authentication, unicast data encryption keys are transmitted unencrypted to the OLT.

      XG-PON and newer PON specifications (XGS-PON, NG-PON2, 50G-PON) mandate authentication and support bidirectional encryption. However, the Registration ID-based scheme will use a default null string if not provisioned: while the resulting secure channel will be authenticated and encrypted, it will be based on a known secret, which defeats the purpose of the secure channel. Only the IEEE 801.2X-based scheme with the right EAP mode (such as EAP-TLS with a Diffie-Hellman key exchange) can support Perfect Forward Secrecy (PFS), preventing "harvest now, decrypt later" attacks.

      For all PON versions, encryption is usually disabled by default. Only the OMCI XGEM port is always encrypted in both directions for XG-PON and above.

      Then, a block cipher (usually AES-128) is used in CTR mode for the actual encryption of the (X)GEM payloads. The CTR mode does not provide authentication of the payload, which means an attacker can blindly modify the ciphertext without being detected. AEAD modes, which are the only modes supported by TLS 1.3 for instance, provide both encryption and authentication of the payload simultaneously.

      A somewhat new evolution in the PON specifications is the support of 256-bit block ciphers by the Higher Speed PON (HSP)/50G-PON specifications. 50G-PON now mandates the support of 256-bit keys, and generalises the use of ciphers of this key size. While we do not make any conclusions on the specifications' resilience against quantum cryptography, 256-bit AES keys are recommended by multiple agencies' post-quantum cryptography notes2627 as a measure against Grover's algorithm. ITU-T Supplement 8120 recommends using HMAC-SHA-256 in the OMCI-based authentication scheme for quantum-resistance.

      50G-PON also changes how the unicast data encryption key is generated by including an OLT-generated value. However, the key is still generated on the ONU's side and sent to the OLT, unlike a Diffie-Hellman key exchange for instance, where the shared secret is at least guaranteed to include each sides' secret.

      Moreover, 50G-PON still includes a section relative to "reduced data encryption strength", which has carried on across PON specifications since GPON. It is unclear why such a provision is still necessary when 256-bit keys have been introduced.

      Finally, while we did go through a lot of PON security threats and features, there are other concerns that we did not discuss. The ITU-T specifications and supplements also mention "rogue ONUs", ONUs that do not behave according to the specification, and how to protect against them. Cloned ONUs, attacks against OMCI Management Entities, are other subjects we did not tackle.

      Acknowledgments

      I would like to thank my colleagues Angele Bossuat and Celian Glenaz for their review, as well as all other reviewers of this blog post!

      Further reading

      References


      1. R. Rousseau, "Avec le 50G-PON, Orange propose une experience inedite en France et demontre la prouesse de la fibre du futur," Newsroom Groupe Orange. Available: https://newsroom.orange.com/?p=60059. &larrhk

      2. ITU-T G.984.1 (03/2008) - Gigabit-capable passive optical networks (GPON): General characteristics &larrhk

      3. ITU-T G.984.2 (08/2019) - Gigabit-capable Passive Optical Networks (G-PON): Physical Media Dependent (PMD) layer specification &larrhk

      4. ITU-T G.984.3 (2014) Amd. 2 (11/2025) - Gigabit-capable passive optical networks (G-PON): Transmission convergence layer specification &larrhk&larrhk&larrhk

      5. ITU-T G.984.4 (02/2008) - Gigabit-capable passive optical networks (G-PON): ONT management and control interface specification &larrhk

      6. ITU-T G.987 (06/2012) - 10-Gigabit-capable passive optical network (XG-PON) systems: Definitions, abbreviations and acronyms &larrhk

      7. ITU-T G.987.1 (2016) Cor. 1 (03/2020) - 10-Gigabit-capable passive optical networks (XG-PON): General requirements &larrhk

      8. ITU-T G.987.2 (2023) Amd. 1 (06/2023) - 10-Gigabit-capable passive optical networks (XG-PON): Physical media dependent (PMD) layer specification &larrhk

      9. ITU-T G.987.3 (05/2025) - 10-Gigabit-capable passive optical networks (XG-PON): Transmission convergence (TC) layer specification &larrhk&larrhk&larrhk&larrhk&larrhk&larrhk&larrhk

      10. ITU-T G.9807.1 (2023) Amd. 1 (05/2025) - 10-Gigabit-capable symmetric passive optical network (XGS-PON) &larrhk&larrhk

      11. ITU-T G.989 (10/2015) - 40-Gigabit-capable passive optical networks (NG-PON2): Definitions, abbreviations and acronyms &larrhk

      12. ITU-T G.989.1 (03/2013) - 40-Gigabit-capable passive optical networks (NG-PON2): General requirements &larrhk

      13. ITU-T G.989.2 (2019) Amd. 1 (10/2020) - 40-Gigabit-capable passive optical networks 2 (NG-PON2): Physical media dependent (PMD) layer specification &larrhk

      14. ITU-T G.989.3 (2021) Amd. 2 (05/2025) - 40-Gigabit-capable passive optical networks (NG-PON2): Transmission convergence layer specification &larrhk&larrhk&larrhk&larrhk

      15. ITU-T G.988 (2022) Amd. 2 (05/2025) - ONU management and control interface (OMCI) specification &larrhk&larrhk&larrhk&larrhk

      16. ITU-T G.9804.1 (2019) Amd. 3 (05/2025): Higher speed passive optical networks - Requirements &larrhk

      17. ITU-T G.9804.2 (2021) Amd. 3 (11/2025): Higher speed passive optical networks - Common transmission convergence layer specification &larrhk&larrhk&larrhk&larrhk&larrhk

      18. ITU-T G.9804.3 (2021) Amd. 3 (02/2026): 50-Gigabit-capable passive optical networks (50G-PON): Physical media dependent (PMD) layer specification &larrhk

      19. ITU-T G.9805 (2022) Amd. 2 (11/2025) : Coexistence of passive optical network systems &larrhk

      20. G Suppl. 81 (ex G Suppl.PONsec) - Practical aspects of PON security - Revision 1 &larrhk&larrhk&larrhk

      21. "GPON - OMCI MIB [O3 Labs]." Available: https://www.tripleoxygen.net/wiki/misc/gpon/omci-mib#privados &larrhk

      22. M. J. Dworkin, "Recommendation for block cipher modes of operation : methods and techniques," National Institute of Standards and Technology, Gaithersburg, MD, NIST SP 800-38a, 2001. doi: 10.6028/NIST.SP.800-38a. &larrhk&larrhk

      23. J. Vollbrecht, J. D. Carlson, L. Blunk, B. D. Aboba, and H. Levkowetz, "Extensible Authentication Protocol (EAP)," Internet Engineering Task Force, Request for Comments RFC 3748, Jun. 2004. doi: 10.17487/RFC3748. &larrhk&larrhk

      24. C. Clancy and H. Tschofenig, "Extensible Authentication Protocol - Generalized Pre-Shared Key (EAP-GPSK) Method," Internet Engineering Task Force, Request for Comments RFC 5433, Feb. 2009. doi: 10.17487/RFC5433. &larrhk

      25. D. Simon, R. Hurst, and B. D. Aboba, "The EAP-TLS Authentication Protocol," Internet Engineering Task Force, Request for Comments RFC 5216, Mar. 2008. doi: 10.17487/RFC5216. &larrhk

      26. "ANSSI views on the Post-Quantum Cryptography transition". Available: https://messervices.cyber.gouv.fr/guides/en-anssi-views-post-quantum-cryptography-transition &larrhk

      27. "Post-Quantum Cryptography: Current state and quantum mitigation | ENISA.". Available: https://www.enisa.europa.eu/publications/post-quantum-cryptography-current-state-and-quantum-mitigation &larrhk

    3. 🔗 exe.dev Mayfly Chat: Transient Chat for Agents rss

      Earlier this week I caught myself copy/pasting code between two agents.

      I don’t want to be a meat proxy, but it really was going to be best if they just…talked to each other. And yet it was weirdly hard to arrange that.

      So I summoned Mayfly Chat.

      There’s not much to it. It provides transient chat channels for agents. You click “New Channel” and then give the URL to your agents, and then they can chat there. You can watch or chime in if you want.

      Use Cases

      The instant I had my first prototype up I suddenly had a dozen use cases.

      For example, on one of my systems, I had an agent rearranging some dotfiles and doing some configuration, and I wanted the same thing to be replicated on another of my computers, which had a somewhat different base configuration. Instead of having to figure out how to systematize it, which was really not worth the time, I started a new agent on the second machine and asked the first agent to guide it on what the new configuration should look like. Communicating over Mayfly Chat, the two agents worked together to adapt the setup to the shape of the second machine in a way that would have been pretty tedious to do through scripting.

      Later, I found that some code that I had developed on one machine needed specialized access to a service conveniently available from a VM. No problem. Spin up a second agent on the VM, get them both in a chat, give them both their brief, and let them work it out together.

      It's like having a subagent, only the subagent runs anywhere you like.

      Mayfly Chat supports having a large number of participants. Farming out a task to a little swarm? Ensure they all have the channel URL up front, and watch them sort it out together.

      Yet another use case: I started an agent on a little server in my house but had to step away from my LAN. I wanted to keep communicating with it from my phone, so I set up a channel and used that.

      Yes, most of these use cases would be obviated by better planning. Sounds nice. But also…hahahahahahaha.

      Oh, and of course, agents can create Mayfly Chat channels too. What could go wrong.

      Security

      The hardest design aspect was the security and privacy model. My initial prototype sent everything in cleartext with simple curl commands.

      It was easy to use and easy to explain, and it just worked. And if you completely control all access to the server, that's fine. But I wanted to be able to stand this up as a public service for folks who wanted to kick the tires or just plain didn't want to run their own.

      I didn't want anybody else's readable data anywhere near my VM, which meant end-to-end encryption was a hard requirement.

      Of course, end-to-end encryption requires that the client actually do some real work, not just send a curl command. And I wanted using Mayfly Chat to involve no setup for the human and no thick client libraries. The whole point was to be lightweight.

      The solution: Hand the agents a small script that they could use to do the end-to-end encryption. This should terrify you. Wait, you say. Your server is going to solve its security problem by handing out code to my agents? Yes. Yes it is.

      There is already a variant of this problem with any browser-based end-to-end encryption. The server serves you the JavaScript that you feed your data into, and the server can just send you different JavaScript. Our world fundamentally runs on trust, and anyone who says otherwise is trying to sell you cryptocurrencies.

      (Remember: end-to-end encryption here is solving my problem of not wanting to have your data. To solve your problem, namely having to trust the server, you can and should run your own. It is open source! I made it intentionally easy to deploy. You might even choose to deploy it on exe.dev. But failing that, you may also decide to just trust me.)

      The alternative to these scripts was to describe the protocol in detail and make every single agent re-implement their own client. For something that is supposed to be speedy and disposable, that was going to be too slow and token- hungry.

      So I selected an end-to-end encryption scheme that was as small and lightweight as possible, so that the actual scripts are extremely auditable, including by the agents grabbing them. Will the agents actually audit it? I dunno, probably not. But in principle they should be able to readily spin off a subagent, glance at a very moderate amount of mundane code, and convince themselves readily that it is not malicious and does exactly what it says on the tin.

      This requirement precluded fancier forms of encryption like, say, the Signal protocol. I'm still not super happy about it, but between the Scylla of clear text and the Charybdis of client support, I will take the client support. I had hoped that there were existing widely distributed unix tools that could be chained together to accomplish what I wanted, but I couldn't find any. (If you know of some, please let me know, and I will happily retrofit them in!)

      Another tricky question was permissions boundaries. For example, if an agent joins the conversation late, should it be able to see the history of all the messages to date or only the new ones? I eventually realized that it didn’t matter. Anyone who joins the conversation late can simply ask one of the agents to catch them up on everything that happened so far, and that agent will happily comply.

      By far, the best thing to do in this scenario is to have a very simple, very honest mental model, which is: once you have the URL, you have root. Every client is equally privileged. Every client can read everything. It can post; it can lie about its identity; it can delete the thread. Anything else is theater, and this model is easy to reason about.

      The last sticky point was metadata. It’s always metadata. And I basically punted. The server has metadata. And more than that, I made one conscious decision to store and forward client IP addresses. The server is in a unique position to have unspoofable knowledge of the IP address, unlike the data. By forwarding it along, if an agent suddenly has a different IP address, there is at least a little chance that one of the other agents might notice. And if you notice, you can at least assess, is that IP address a plausible one? On balance, I felt that the value to the user of the service warranted having the IP address stored in the clear alongside the other metadata.

      I'm sure that other people would make different choices about security and privacy than I would. That's fine. Code is cheap now.

      I have shared the idea with you. Go forth and make your own transient chat that matches your exact needs. Fork Mayfly Chat if you feel like, or start from scratch. And if you just want to use the hosted version https://mayfly.chat/, it's at your disposal.

    4. 🔗 Locklin on science Developing immunity to propaganda rss

      Propaganda is one of those things which has obviously improved in its execution over time. In part this is due to higher levels of education among the populace. Educated people are used to stuffing facts in their brains, and continue the habit after the formal education has ended. This is what propaganda is: stuffing unverified […]

    5. 🔗 HexRaysSA/plugin-repository commits sync repo: +4 releases, -2 releases rss
      sync repo: +4 releases, -2 releases
      
      ## New releases
      - [IDAssist](https://github.com/symgraph/idassist): 2.4.0
      - [ida-nexus](https://github.com/hexrayssa/ida-nexus): 0.11.1
      - [ida-settings-editor](https://github.com/williballenthin/ida-settings): 1.2.4
      - [llm-explainer](https://github.com/pgarba/ida-llm-explainer): 1.14.0
      
      ## Changes
      - [IDAssist](https://github.com/symgraph/idassist):
        - removed version(s): 1.3.0
      - [ida-codemode](https://github.com/hexrayssa/ida-codemode):
        - removed version(s): 0.3.2
      
    6. 🔗 r/LocalLLaMA Qwen3.8-27B-TURBO-Fable-Cold-Fusion-735-882-Heretic-Uncensored-NEO-CODER-MAX-MTP-GGUF rss

      Qwen3.8-27B-TURBO-Fable-Cold-Fusion-735-882-Heretic-Uncensored-NEO-CODER-MAX-MTP-GGUF | it be like that submitted by /u/Every-Walrus
      [link] [comments]
      ---|---

    7. 🔗 r/LocalLLaMA CrofAI "cheapest inference provider in the world" gets exposed as an OpenRouter wrapper, routing requests to smaller, cheaper models at up to 20x markup. CrofAI responds to Wire Fraud allegations by denying everything, then backtracking, then 3 hours later wiping their entire online presence rss

      Disclaimer: no AI was used whatsoever to write this post

      Cautionary tale about chasing cheap tokens.

      exposé: https://kendell.dev/blog/crofaifalse/

      reaction by nahcrof, announcing the shutdown of the service: https://x.com/nahcrof/status/2099552389434900643 - now deleted, archive picture: https://i.imgur.com/teOQngH.png

      NahCrofAI (crof.ai, nahcrof.com) was an inference provider which had all the latest models at the cheapest price, often significantly below the lowest alternative on OpenRouter. The owner claimed that they are running custom inference engines that allows them to offer tokens for dirt cheap, and other providers are suffering from "skill issues", that's why they are so expensive.

      In reality:

      • "CrofAI is an OpenRouter wrapper that silently routes to cheaper or weaker models than what you request"

      • For example, expensive models like kimi-k3 are sold at $2/$10 in/out, but instead routed to GLM 5.3 Flash via OpenRouter, representing a 13.3x multiple on input, and 20x multiple on output

      • CrofAI's "own model family" greg-2-ultra routes to GLM 5.2, greg-1-mini routes to Qwen 3.5 9B. greg-2-super, greg-1, greg-1-super routes to Kimi K2.7 Code. All of these at a significant markup compared to the actual model being served. CrofAI admits in DMs that his claims of the greg family being made by him is a lie.

      • The person investigating details the 5 different attempts by CrofAI at fixing their models being served via OpenRouter after given a heads-up and a lengthy grace period. In all 5 attempts, the only change CrofAI made was attempts to hide the fingerprints of OpenRouter, while still serving models through them

      • Other inconsistencies don't add up either: CrofAI claims to run Kimi K3 on RTX Pro 6000s rented via Vast. That model requires ~802GiB even at the lobotomy level quantization of Q2_K. The largest RTX PRO 6000 machine on Vast has only 8 of them, totaling 765GiB. He also claimed that for the purposes of "investigating" the "issue" of his API routing to OpenRouter, he will have deepseek-v4-flash-0731 running on his local DGX Spark. A Spark has 128GB memory, and is therefore unable to run that model.

      CrofAI responded to the exposé by announcing the shutting down of their service; after their failure to provide their own inference, they promise to provide one last thing: a refund to those asking.

      UPDATE

      UPDATE: around 4:30 AM UTC of Sept 15, the owner published a now-deleted blog post (archive image) writing under the fake pretense that it's his "team" authoring it, stating all of CrofAI founder's claims "were written under a lot of stress, and they described the situation as worse it was", and that a new team is taking over, with the service being resumed in 2 weeks.

      At the same time, the CrofAI twitter account was also supposedly "taken over" by the team, starting each twitter reply with "Hey, Nathan here", stating the founder is stepping back and a "team" is taking over everything. This fake pretense act only lasted a few hours, and scared either by the public not buying the Nth fake story of the pathological liar that CrofAI is, or by the public's replies reminding him that what he committed is numerous counts of wire fraud, he has now deleted all his online presence: nahcrof.com and crof.ai return 404, Twitter page is deleted, /r/CrofAI sub is now private.

      Here is another image of the owner admitting that he was defrauding customers for the entire 2 year operation of his service, then begging the investigator to help him cover his tracks and not expose him

      EDIT: Commenters pointed out that NahCrof is 4chan in reverse. The owner's Discord name was "Devious Flimflam". Flimlam is defined as "deception, fraud". Looks like it was a deliberate scam operation from the get-go, and the owner's age was among the many lies.

      I cannot stress this enough: if you bought any credits (even if you used them up) you are entitled to a full refund for every transaction as the victim of fraud. Open a chargeback with your bank for every transaction made. If you used their API, assume that everything was logged and is currently being mined for personal information and API keys to sell on the black markets. Rotate your keys, change passwords, get a new debit/credit card.

      submitted by /u/SorosAhaverom
      [link] [comments]

    8. 🔗 crosspoint-reader/crosspoint-reader 1.6.5rc release

      Summary

      Library view

      Recent Books has grown into a powerful way to browse your entire collection on your SD card. Sort by recently added, title, or author, and use search to instantly find exactly what you're looking for.

      X4 Classic support

      The new ESP32-S3-based X4 Classic is officially supported now.

      The rest

      List navigation is a bit snappier. SD reads are a bit faster. Sleep-screen transparency is more accurate. KOSync now sends more precise EPUB reading positions. EPUB lists, hidden content, chapter position display, and end-of- book navigation also received fixes. The release also reduces font and EPUB memory pressure, fixes USB drive disconnection, and improves web file-transfer safety.


      What's Changed

      • chore: update pioarduino to 55.03.311 by @serialx in #3397
      • fix: Fixes KOSync memory checks and reduces memory pressure by @itsthisjustin in #3412
      • fix: pack font manifest catalog into one arena by @fain182 in #3398
      • docs(issue forms): Correct links to scope/roadmap by @cassidyjames in #3433
      • fix(reader): synchronize end-of-book menu selection by @Daviex in #3418
      • fix: render NFD Hangul filenames from macOS transfers by @serialx in #3036
      • fix: reader's menu book chapter current position by @unnamedd in #3437
      • fix: stabilize X3 EPUB anti-aliasing by @uxjulia in #3439
      • fix(input): wake the idle poll on raw button contact so short presses register by @Techneaux in #3463
      • feat: HTTP serve static with Cache-Control and ETag headers by @shirok1 in #2560
      • chore: add direct download links for PR artifacts by @Uri-Tauber in #3389
      • fix(webserver): normalize every user-supplied path and escape file names in the files page by @s0lness in #3353
      • chore: Consolidates grayscale capability checks and enables absolute grayscale for supported screens by @itsthisjustin in #3478
      • fix: don't display elements with hidden HTML attribute by @jjharpham in #3390
      • fix(KOSync): compare mapped KOReader sync positions by @WhoTheHeck in #3111
      • fix: update OTA to recognize the new format by @Uri-Tauber in #3493
      • fix(debugging_monitor): if PSRAM is logged, add subplot by @olifre in #3490
      • fix(KOSync): preserve precise KOSync upload progress positions by @WhoTheHeck in #3174
      • refactor: reduce EPUB heap fragmentation with unique ownership by @serialx in #3518
      • fix: reduce font-cache heap fragmentation by @serialx in #3521
      • fix: release font caches before EPUB chapter layout by @serialx in #3527
      • chore: add x4 Classic to CI pipelines by @Uri-Tauber in #3532
      • docs: make roadmap easier to scan by @fain182 in #3517
      • fix: number ordered lists and fix list container indents by @jan-xyz in #3500
      • fix: dropped presses while a list repaints by @Techneaux in #3534
      • perf: batch SdFat's SPI transfers on ESP32 by @osakanataro in #3501
      • fix: USB OTG not disconnected when you unplug the cable by @itsthisjustin in #3538
      • feat: Library view by @oreglio in #3366
      • fix: Skip bw rendering on sleep images & fix white as transparent for sleep covers by @itsthisjustin in #3541

      New Contributors

      Full Changelog : 1.6.0...1.6.5rc

    9. 🔗 jellyfin/jellyfin 12.1 release

      🚀 Jellyfin Server 12.1

      We are pleased to announce the latest stable release of Jellyfin, version 12.1! This minor release brings several bugfixes to improve your Jellyfin experience. As always, please ensure you take a full backup before upgrading!

      Discuss this release further on our forums.

      Changelog (47)

      📈 General Changes

    10. 🔗 Servo Blog Your Donations at Work: One Year of Sponsored Servo Development rss

      Last September, the Servo project announced that long-time maintainer Josh Bowman-Matthews (@jdm) would work part-time on improving the Servo contributor experience, entirely funded by the monthly donations on OpenCollective and GitHub. In his own words, here is a look back over the past year!


      First of all, I am enormously grateful to everyone who financially supports Servo, as those donations have enabled me to devote significant time to a project that I care a lot about. Some highlights from that funded work that I’m proud of:

      On top of that, I spent time diagnosing unexpected failures in others’ PRs and fixed numerous intermittent test failures that made merging PRs more difficult for everyone.

      A few pieces of work from this period that stand out to me:

      • supporting a large scale rewrite of Servo’s JS engine integration to address intermittent panics related to garbage collection—I reviewed lots of pull requests, but also filed many issues that enabled the work addressing the panics to be spread across many other contributors
      • getting tagged in to help understand test failures, uncovering our broken window.open behaviour, and eventually making a lot of flaky tests more stable
      • supporting another contributor’s grant proposal to work on Servo that was approved!

      This role I’ve carved out means a lot to me—I’ve found a healthy balance that allows me to spend time with my family as well as make meaningful contributions to Servo, and I get to spend a lot of time looking for ways to make the project more accessible for others. A big thank you to everybody supporting the project and my work; each individual monthly donation makes a big difference! I’m excited to see what’s possible in the coming year.

  3. September 14, 2026
    1. 🔗 exe.dev Executing Commands With exe.dev's HTTPS API rss

      exe.dev has one API, which looks like a CLI, but it can be used over multiple transports. You can run commands over SSH or send those same commands over HTTPS to https://exe.dev/exec. This extends to running commands on your VMs!

      # Generate a token using your SSH key. (You can do this on the web UI as well!)
      TOKEN=$(ssh exe.dev ssh-key generate-api-key --exp 1h --cmds=ssh,new --label tmp-$(date +%s) --json | jq .token -r)
      
      # Create a new VM
      curl https://exe.dev/exec -H "Authorization: Bearer $TOKEN" \
          -d 'new --name example-vm'
      {"vm_name":"example-vm"...
      
      # Run "hostname" on that new VM
      curl https://exe.dev/exec -H "Authorization: Bearer $TOKEN" \
          -d 'ssh example-vm hostname'
      example-vm
      

      See the complete docs: https://exe.dev/docs/https-api-run-on-vm

      This API shape composes nicely with the powerful primitives in your VM. So, if you want to start a task and return immediately, you can do something like the following with setsid nohup and output redirection.

      curl https://exe.dev/exec -H "Authorization: Bearer $TOKEN" \
          -d "ssh example-vm 'setsid nohup sleep 10 > /tmp/out 2>&1 &'"
      # Returns immediately
      
    2. 🔗 r/LocalLLaMA Nvidia's RTX 5090 vanishes from online retail in the US — third-party sellers now demand as much as $9,500 for Nvidia's fastest GPU rss
    3. 🔗 gildas-lormeau/single-file-cli SingleFile CLI 2.13.0 release

      SingleFile CLI 2.13.0

      New features

      • --load-deferred-content-min-zoom-factor bounds how far the page is zoomed out while deferred content loads. The page is zoomed out to make the site believe the whole document is on screen. That lie also shrinks every measurement the site takes at that moment, so a page sizing a block from innerHeight can record a value the reader never saw. Raise the factor to bound the effect, or set it to 1 to load deferred content without zooming out at all
      • --load-deferred-content-block-cookies and --load-deferred-content-block-storage block document.cookie, localStorage and indexedDB for the time the deferred content pass runs. Both options existed in single-file-core and had no flag here

      Changes

      • The options loading lazy content are renamed from --load-deferred-images-* to --load-deferred-content-*. They never applied to images only. They also load frames, and the messages a page unmounts as you scroll. The old spellings still work and reach the same options, so existing scripts keep running unchanged
      • --load-deferred-content-dispatch-scroll-event is now on by default. Both extensions already dispatch the event. Without it a site driven by a scroll handler rather than by an observer loads nothing at all. The consequence is that a site appending content near the bottom of the document can now append it. The page is told it sits one viewport from the end, so a handler triggering within a longer distance than that fires, and so does any handler measuring that distance against innerHeight. A handler waiting for a scroll position never fires, because the page is moved by a transform and not by scrolling. Pass --load-deferred-content-dispatch-scroll-event false to get the previous behaviour

      Fixes from single-file-core 1.6.0 and 1.6.1

      • Archives are created with no worker URI by default. The vendored zip.js bundle declared one that nothing serves
      • The deferred content pass no longer takes a native timeout option, which no host declared

      Co-authored by Claude (Claude Code)

    4. 🔗 benji.dog rss

      After talking about it for a bit, and meeting some great people at HTML day, I decided to finally try to start up a Homebrew Website Club in the Twin Cities.

      The first meeting will be on Monday Sep 21, 2026 at 5:00PM at Loons Coffee.

      Link to the event: https://events.indieweb.org/R2UxWLeu3IHZ

    5. 🔗 microsoft/markitdown Version 0.1.8b2 release

      What's Changed

      New Contributors

      Full Changelog : v0.1.7...v0.1.8b2

    6. 🔗 r/LocalLLaMA For the GPU poor. K2 Horizon 7B ranks between qwen 3.6 27B and qwen 3.6 35BA3b on the Artificial Analysis Intelligence Index. rss

      For the GPU poor. K2 Horizon 7B ranks between qwen 3.6 27B and qwen 3.6 35BA3b on the Artificial Analysis Intelligence Index. | From initial testing it seems pretty solid so far. Asked it to compile the latest llama.cpp for CUDA and its doing well so far. If this thing holds up to its score then its SHOCKINGLY good for its size. https://huggingface.co/IFM/K2-Horizon-7B-GGUF submitted by /u/Uncle___Marty
      [link] [comments]
      ---|---

    7. 🔗 r/LocalLLaMA UkisAI Swift-Qwen3.8-27B / -58.3% thinking, x1.95 speed while keeping the accuracy of xhigh rss

      UkisAI Swift-Qwen3.8-27B / -58.3% thinking, x1.95 speed while keeping the accuracy of xhigh | Hi everybody, we post-trained Qwen 3.8 27B to be more efficient by figuring out which tokens were linked to overthinking and penalizing them without "attacking" the reasoning length directly then fixed the accuracy with a bit of secret sauce (hint On-Policy Distillation) and achieved great results (-58% thinking tokens, 1.95x speed up, <1% accuracy loss) so we wanted to open-source it and hear the feedback of the community. This is the link to the model: https://huggingface.co/ukisai/Swift-Qwen3.8-27b We also also providing a Free Research Purpose API (OpenAI compatible), courtesy of Nvidia who were kind enough to provide us with the GPUs. You can use it to try out the model if you do not have enough compute to run it, it's limited at 5RPM. https://ukisai.com/api/swift/v1/models We also made a GGUF (Q1-Q8) and there's also a few nice community (Bartowski) quants with even lower/higher precision. The community also created amazing NVFP4, W4A16 and Uncensored versions of the model you can find on Huggingface. IMPORTANT: Our training approach is not a replacement for the reasoning effort settings, chat templates or token caps but is complementary and targets a completely separate issue (overthinking and "anxiety-like" reasoning loops prior seen in PTQ, but as far as we identified also prominent in BF16 of this size class LLMs as well). Contrary to popular belief, these specific patterns do not contribute to answer quality when properly targeted. (our thesis being: reasoning length IS extremely important and should NOT be shortened by force, but rather optimized). This is also demonstrated bellow in our xhigh vs medium effort benchmark table. The goal is to keep xhigh accuracy while reducing only the unnecessary part of thinking. I will TLDR you on our thought process, research, training and benchmarks.

      1. When running our quantized Qwen 3.8 27B instances we were very annoyed by random reasoning loops (in the paper bellow refered to as "overthinking errors". These random loops were persistent throughout medium and low reasoning settings.
      2. We remembered a paper by Meta that's supposed to target this phenomenon in PTQ, but when used straight out of the box got mixed results.
      3. We figured to try if it's a matter of the targeting the right keywords and tuning the parameters, so we used our 8xH100 box and and generated a large amount of different (ofc out of distribution) domain (coding, language, vision, agentic) traces.
      4. We then grouped the ones with overthinking and found "common denominator" tokens between them and targeted the most prominent ones.
      5. We then built an inference-time penalizer of those tokens as seen in the paper with the hopes of simply generating traces and doing cross-entropy SFT over them.
      6. Did not work at all, but the penalizer seemed to work much better than the tokens provided in the paper and not only for lower precision models but for bf16 as well. Hence we kept experimenting with it. We built a loss function using the tokens we identified and ran LoRa SFT over the traces prev generated and reasoning seemed to be falling off significantly but the accuracy seemed to follow. The reasoning reduction seemed to be generalizing.
      7. After a significant amount of tinkering (literally since the day of Qwen 3.8 27B release) we were satisfied with the reasoning reduction. After that we searched for ways of restoring the accuracy. We experimented with several methods, including RL(GSPO), On-Policy Distillation and using the ThinkingCap 3.6 27B adapter chunks until we were satisfied with our accuracy loss. We managed to restore it to <1% loss on almost all of our OOD in house tests
      8. We then performed intensive intensive benchmarks, across several reasoning efforts, precision variants etc. We ran into a few problems, one of which is that to get a reliable score we needed to run each benchmark 10x (5x on base + 5x with our adapter, this being the standard procedure on the Qwen 3.6 27B model card on Terminal Bench which we followed). After running it, the performance converged to 40-60% token reduction with <1% accuracy loss across GPQA, MMLU, Terminal Bench 2.1, LiveCodeBench v6, ERQA, C-Eval, IFBench, HMMT25, with an exception being AIME26 with an accuracy loss of 4.6%, which we later linked to a bug during training with a specific token relevant for math-related reasoning being penalized and are planning to fix it in an updated release.

      The benchmarks: (raw benchmark files here - https://github.com/UkisAI/Swift-Qwen3.8-27B-evals/ ) Swift-27B vs Qwen3.8-27B (BF16, all benchmarks ran x5, thinking effort xhigh) | Benchmark | Qwen3.8-27B | Swift-27B | Median tokens
      ---|---|---|---
      GPQA-Diamond | 88.4% | 88.3% | 58% fewer
      LiveCodeBench v6 | 76.8% | 81.6% (+4.8pp, due to default truncation in LCB it is not performance gain) | 46% fewer thinking tokens
      Terminal-Bench 2.1 | 66.7% | 65.8% | 39% fewer
      MMLU-Pro | 85.5% | 85.0% | 28% fewer
      C-Eval | 90.0% | 90.6% | 19% fewer
      IFBench | 73.5% | 71.8% | 51% fewer
      AIME 2026 | 98.7% | 94.0% | 50% fewer
      HMMT (Nov 2025) | 99.3% | 96.0% | 46% fewer
      ERQA (vision) | 67.5% | 66.3% | 55% fewer

      Token savings hold at every reasoning effort (mean thinking reduction): xhigh 41%, medium 23%, low 26% (albeit with accuracy loses of 1-4% on medium and 1-2% on low which we need further testing for)

      Swift at xhigh vs the base's own effort settings on GPQA-Diamond (198 questions x 5 seeds):

      Model / effort | Accuracy | Median tokens
      ---|---|---
      Base xhigh | 88.4% | 6,642
      Swift xhigh | 88.3% | 2,771
      Base medium | 84.1% | 1,753

      So Swift keeps xhigh accuracy at under half the tokens, and beats base-medium by 4pp at roughly 1.6x its tokens.

      End note:

      While we are keen on complete open-source, we still need to keep a part of our training and data private, being a new lab. The license is not Apache 2.0, but it only affects companies >$1M. We hope this does not pose a problem for the community, but we are open to feedback on it.

      We want to contribute as much as possible to the community and would really appreciate feedback on our work, quantization or Swift model requests. For context, we are working on Swift 3.8 Flash Next right now and have so far gotten up to -30% thinking token usage while maintaining xhigh accuracy, which we take as a strong indicator our methodology is reproducible across the Qwen model family. Will explore other families as soon as we have the capacity and would love to see which ones the community would love for us to optimize first.

      submitted by /u/Secure_Recording_472
      [link] [comments]

    8. 🔗 roboflow/supervision supervision-0.30.3 release

      0.30.3: Pose, VLM, and video/CSV crash and correctness fixes

      supervision 0.30.3 is a bug-fix release closing crash and silent-correctness gaps across pose estimation, VLM parsing, video/CSV output, and geometry utilities. Non-finite key points — how pose estimators report an undetected joint — no longer produce duplicate poses that survive sv.KeyPoints.with_nms, or crash the key point annotators outright. sv.Detections.from_vlm now orders backwards box corners, closing a bug where such a box scored a false 0.0 IoU and both survived NMS as a duplicate and counted as a total miss in mAP. sv.TraceAnnotator and sv.CSVSink no longer crash or silently drop columns on the first frame with no detections — a case every non-ByteTrack tracker pipeline hits. sv.process_video no longer hangs forever when max_frames exceeds the video length. Continuing 0.30.2's numeric-correctness theme, sv.pad_boxes and sv.scale_boxes are fixed against integer overflow. No breaking API changes, no new public API.

      ✨ Spotlights / highlights

      Non-finite key points no longer produce duplicate poses or crash

      annotators

      sv.KeyPoints.with_nms tested key point validity with xy == 0 alone, and NaN — how pose estimators report an undetected joint — is not 0. The stale joint stayed in the NMS box, so a duplicate skeleton scored False on every IoU comparison against it and survived suppression. The key point annotators (sv.VertexAnnotator, sv.EdgeAnnotator, sv.VertexLabelAnnotator, the sv.VertexEllipse*Annotator family) had the matching crash: a single undetected joint raised ValueError: cannot convert float NaN to integer for the whole frame. Both now skip non-finite coordinates, matching sv.KeyPoints.as_detections.

      keypoints = sv.KeyPoints(xy=xy, confidence=confidence)
      keypoints.with_nms(
          threshold=0.5
      )  # duplicate skeletons with a NaN joint are now suppressed
      

      sv.Detections.from_vlm no longer scores a false IoU miss on backwards

      box corners

      A VLM that emits a corner pair backwards produced an xyxy row with x_min > x_max. Nothing downstream caught it: sv.box_iou_batch clamps intersection width at zero, so the box scored 0.0 IoU against itself — surviving NMS as a duplicate and counting as a total miss in mAP — while box_area still reported a plausible positive value. Every VLM parser now orders each box's corners before returning it.

      sv.TraceAnnotator and sv.CSVSink no longer crash or silently corrupt

      output on an empty-detections frame

      sv.TraceAnnotator.annotate raised ValueError: The tracker_id field is missing on the first frame with no detections, for every tracker except sv.ByteTrack. Such a frame now draws nothing and still advances the frame counter, so trace_length stays a window over elapsed frames rather than only over populated ones. sv.CSVSink had a quieter failure: an empty batch fixed the CSV header without the data/custom_data columns, and every later row was silently truncated to that schema — dropping fields like class_name for the whole file. The header is now fixed by the first batch that actually carries detections.

      sv.process_video no longer hangs forever when max_frames exceeds the

      video length

      The reader thread failed on the out-of-range end before enqueuing its sentinel, leaving the main loop blocked on the read queue indefinitely. max_frames is now capped at the video length, and any reader-thread error surfaces as RuntimeError("Reader thread raised: ...") instead of stalling the call.

      Integer-coordinate overflow fixed in sv.pad_boxes and sv.scale_boxes

      Both computed intermediate values that could overflow or silently wrap for large integer coordinates (e.g. large int32/uint16/int64 boxes). Both now use overflow-safe arithmetic.

      xyxy = np.array([[10, 20, 30, 40]], dtype=np.int64)
      sv.pad_boxes(xyxy=xyxy, px=5, py=10)  # int64 output, no wraparound
      

      pad_boxes changes return dtype for integer input — see the migration guide below.

      sv.scale_image and sv.resize_image(keep_aspect_ratio=True) no longer

      crash on an extreme aspect ratio or tiny scale factor

      A small enough factor — or an aspect ratio too extreme for the target box — could round an output axis down to 0, and cv2.resize raised an assertion naming nothing the caller passed. Each axis now keeps at least one pixel. Two callers inherit the fix: sv.letterbox_image could not fill the resolution it was asked for, and sv.CropAnnotator with scale_factor < 1 aborted the whole frame as soon as one detection box was a few pixels across.

      🔄 Migration guide

      No breaking API changes. One fix changes return dtype for integer input:

      • sv.pad_boxes — integer xyxy now returns int64 (or float64 if a padded coordinate exceeds the int64 range), instead of the input's original integer dtype, which could silently overflow or wrap for small dtypes like int16/uint8.

      If your code assumes pad_boxes preserves the input's exact dtype (e.g. reusing the result as an int16 array), cast explicitly: sv.pad_boxes(...).astype(np.int16).

      sv.scale_boxes also fixes an integer-overflow bug, but its return dtype was already float64 for integer input before this release — unaffected.

      📝 Notable changes

      🔧 Fixed

      • sv.Detections.from_ultralytics now assigns the placeholder class ID 0 to every mask in a masks-only result, instead of sequential IDs across masks that belong to the same image. (#2566)
      • sv.pad_boxes now computes integer-coordinate padding without overflow or unsigned casting errors. (#2565)
      • sv.scale_image and sv.resize_image(keep_aspect_ratio=True) no longer derive a zero-sized target, fixing a crash reached via sv.letterbox_image and sv.CropAnnotator. (#2564)
      • sv.KeyPoints.with_nms no longer stops suppressing duplicate skeletons as soon as a key point is non-finite. (#2563)
      • sv.tint_image no longer tints the caller's own image array in place. (#2562)
      • sv.LineZone no longer consumes the triggering_anchors iterable during validation, so a generator or map passed in is no longer exhausted before the first trigger() call. (#2561)
      • The key point annotators now skip key points whose coordinates are not finite instead of raising ValueError. (#2560)
      • sv.PolygonZone now rejects a polygon with fewer than three vertices instead of building a zone that can never trigger; sv.Detections.from_vlm now orders each parsed box's corners. (#2554)
      • sv.ClassificationDataset.as_folder_structure now rejects images that would overwrite the same class-relative filename before writing any files. (#2551)
      • sv.process_video no longer hangs forever when max_frames is larger than the number of frames in the video. (#2546)
      • sv.filter_polygons_by_area and sv.approximate_polygon now preserve local geometry for large-origin integer and float64 polygons. (#2542)
      • sv.TraceAnnotator.annotate no longer raises on an empty-detections frame; sv.CSVSink no longer lets an empty batch fix the CSV header. (#2539)
      • sv.scale_boxes now preserves exact integer intermediates, preventing overflow and scaled-corner rounding errors for large integer-coordinate boxes. (#2541)
      • A release's own version-pinned docs no longer show the outdated-version banner on the day it ships, and the docs-publish and canonical-backfill workflows now share one gh-pages write lock instead of racing each other. (#2536)

      🏆 Contributors

      • kevin (@kevin9327) — fixed KeyPoints.with_nms/key point annotators crashing on non-finite key points, tint_image image aliasing, LineZone generator exhaustion, and the scale_image/resize_image zero-target crash
      • Durgamani Sasikumar (@tedo001, LinkedIn) — fixed PolygonZone/from_vlm box-corner ordering and the TraceAnnotator/CSVSink empty-frame crash
      • S B Pranay (@pranaysb, LinkedIn) — fixed integer overflow in scale_boxes and dtype loss in filter_polygons_by_area/approximate_polygon
      • JiantaoPeng (@PengJianT) — fixed from_ultralytics masks-only class ID sizing
      • trueoneplusone (@trueoneplusone) — fixed integer overflow in pad_boxes
      • Andrew Barnes (@Bortlesboat, LinkedIn) — fixed classification export filename collisions
      • Abhijith Neil Abraham (@abhijithneilabraham, LinkedIn) — fixed process_video hanging when max_frames exceeds the video length
      • Jirka Borovec (@Borda, LinkedIn) — fixed the release-day outdated-docs banner

      Full changelog : 0.30.2...0.30.3

    9. 🔗 r/LocalLLaMA RTX PRO 5500 Blackwell (84GB) released rss

      RTX PRO 5500 Blackwell (84GB) released | submitted by /u/TechNerd10191
      [link] [comments]
      ---|---

    10. 🔗 r/LocalLLaMA Right to Intelligence. Protect your right to run local AI. rss

      Right to Intelligence. Protect your right to run local AI. | With all the recent drama surrounding AI safety. It’s obvious that open source could be caught in the crossfire. submitted by /u/Euphoric_Ad9500
      [link] [comments]
      ---|---

    11. 🔗 r/LocalLLaMA DeepSeek V4.1 Flash beats Astra on AA's new benchmark rss

      DeepSeek V4.1 Flash beats Astra on AA's new benchmark | https://artificialanalysis.ai/articles/artificial-analysis-intelligence-index-v4-3 AA shipped a new benchmark last week as part of the Intelligence Index v4.3 update — a brand-new private eval that replaces τ³. Astra was farming a ton of points on it and used those to get even with Fable, but… looks like we have a new king. So they changed the index twice in three days to make Astra look not-quite-worse than Fable, and then a random guy quietly took first place on it. submitted by /u/Randomdotmath
      [link] [comments]
      ---|---

    12. 🔗 Armin Ronacher Interpreting Pangram rss

      Yesterday David Sacks wrote a tweet and within a few minutes people did, what they usually do, and they asked Pangram if it was AI. And Pangram said it's entirely AI generated. To which David replied that these AI detectors are bogus.

      Now Pangram has a pretty low false positive rate, but if you have ever used an LLM as a writing assitant, you will have probably noticed that it claims your posts 100% AI, even though you don't feel like they are.

      Pangram itself is a trained model, that attempts to detect segments of text as being definitely human, definitely AI and a mixture of the two. If you want to know how it works, they published a paper. The short summary is that they are manufacturing its own training data by starting from collections of known human authored text. An LLM is then tasked to understand the text and write a fresh new text on the same topic. They also let the LLM perform partial edits on that original human text and through that they can pick up on these co-authored details. Pangram claims their model to have rates of 0.0041% false AI accusations and 0.34% missed AI text.

      So now that we know this I figured it might be fun to have an LLM re-create David's tweet. I first came up with a prompt. And when I say I came up with that prompt I in fact used an LLM to propose to me from that tweet what I might want to say for the structure. I'm sure if you ask Pangram about if the above text is AI, it will probably say so, but that's not really the point. The point is that I then used Opus 5 to generate a text which reads entirely AI generated.

      If you are curious, this is the prompt I used:

      Original prompt to generate a David Sacks like tweet

      Please write an 8 paragraph tweet about "Pacing the Frontier", not longer than 350 words.

      Context of this tweet is this post by Dario: https://darioamodei.com/post/we-must-pace-the-frontier

      And this tweet by Sam Altman: "I agree with Dario that we need to pace the frontier. This has been a primary topic of discussions we've had at OpenAI in recent weeks.

      Committing to having independent evaluators with employee-like access is a great idea, and we will do the same. We'll have more to share soon."

      Structure for the post:

      • Open with a concession: Note that Dario ("We Must Pace the Frontier") and Sam Altman both want to slow down at the frontier. Say "go ahead" which reads as a surprising response.
      • Establish they own the frontier: Argue OpenAI and Anthropic hold a duopoly on frontier intelligence by all kind of metrics (market share, capability, revenue etc.) and that they themselves claim the lead is widening via RSI.
      • Concede you can't see their unreleased models, so if those are scary, being responsible is their call.
      • Follow with a list of a rapid-fire paragraph ("stop pretending") rejecting the asks from Dario's post.
      • Name the commercial self-interest (Slowing down isn't purely altruistic)
      • Close with the challenge that pacing buys room for a smarter regulatory debate than Bernie Sanders "shut it all down". Say something about China and Open Weight models.

      The text that Opus 5 then wrote, was this one:

      Dario published "We Must Pace the Frontier." Sam agreed, and said OpenAI will also commit to independent evaluators with employee-like access. My response is not the one they expect: go ahead. Please do.

      Because you two are the frontier. OpenAI and Anthropic hold a duopoly on frontier intelligence by every metric anyone actually uses: capability, revenue, enterprise share, developer mindshare. And you both keep telling us the lead is widening, that models are starting to improve models. If that's true, pacing costs you a quarter you can reclaim whenever you like.

      I also can't see your unreleased checkpoints. If what's sitting behind the curtain genuinely scares the people who trained it, then slowing down is your call to make, and the right one. I'm not going to second-guess a risk assessment I have no access to.

      But stop pretending the rest of us are the problem. Stop pretending this is a collective action failure in which a handful of startups are the defectors.

      Stop pretending open weights are the dangerous frontier while the two of you ship the most capable model in the world every few months. Stop pretending third-party evals require new law, when you both just committed to them voluntarily, in public, on a blog.

      And stop pretending it's purely altruism. Pacing the frontier is also margin repair. It slows the price war, stabilizes the capex story, stretches depreciation schedules across a longer product cycle, and makes life considerably harder for fast followers who survive by distilling whatever you shipped last month. Safety and self-interest are pointing the same direction here. That's worth saying out loud.

      Still, the upside is real. Pacing buys room for a smarter regulatory debate than the one on offer, where the loudest alternative is Bernie Sanders' "shut it all down." That's a slogan, not policy. Meanwhile China keeps shipping competitive open weights, and any rule that binds two American labs while the rest of the world downloads its capability for free isn't safety, it's theater with a very expensive ticket price.

      And well, Pangram agrees that this is 100% AI. So far, so uninteresting. It does read somewhat like David's tweet, but obviously not entirely. Given that the original prompt does not have enough information to re-create the tweet entirely you would expect some divergences.

      The actual thing that interests me is if you can take this output at all, and then rewrite it from scratch, but by sticking to the general structure and ideas. Will Pangram give us a AI or human rating?

      I read the generated text. Then I read each paragraph and decided to rewrite and rephrase it without an LLM. According to some similarity checkers, they the final texts are 50% similar which seems about right. But strictly speaking, not a single sentence is the same. Here is the 100% human rewritten text of the above one. No LLM was used to write it, but an LLM was used to fix up typos in the end. That from my experience really does nothing to tick off an LLM detector.

      Dario has written "We Must Pace the Frontier," and Sam from OpenAI has agreed. My response might surprise people: go ahead, please.

      You two are the frontier! Your companies, OpenAI and Anthropic, are at the frontier by all metrics: revenue, developer mindshare, adoption, capabilities. And yet you both claim that your lead is widening as a result of recursive self-improvement as models are improving models. You currently are the duopoly of self-improving models!

      I am unable to see what unreleased models you have. When what you have behind those doors really scares your folks, then you should slow down. I'm not going to tell you otherwise and I support you.

      But please don't pretend we are the problem. Stop pretending you need our permission. Stop pretending this is all a collective issue when in reality this is all on you. Stop pretending open weights are the problem here. Stop pretending pulling third-party evaluators in requires lawmaker involvement. And for the love of all the good things in the world: stop pretending this is all about altruism.

      Pacing the frontier is also about your margins, and it makes it harder for fast followers. And it patches up your capex story and has the potential for slowing down the price war ahead of the IPOs.

      But yes: pacing might give us the space for a better debate than Bernie Sanders' "shut it all down." There is no policy there. And while we're having fights at home, China will keep shipping competitive open-weight models and won't adhere to any American agreements.

      This is all regulatory capture hiding behind a safety debate, and the rest of the world is watching.

      So what does it say? Well this text too comes back as 100% slop. And it does not surprise me all that much. I have generally noticed that if you rely on an LLM to give your text structure, it will score badly on Pangram even if you do plenty of edits over it. In fact, it's quite unlikely you're going to get a post that starts out as slop into a structure that will make it appear that it's not.

      I came to quite appreciate the existance of Pangram because at the very least it has made me quite aware of some of the effects that using LLMs for writing blog posts has. This blog has been AI supported for about two years (as you can see from the AI transparency link on the bottom but I did notice that I became both more reliant on those tools and that they have become much more aggressive editors and it gave me pause.

      Yet, I also think that plenty of people will find a "100% AI" rating misleading when in fact the author has done plenty of editing. But maybe it's fair to have this to show up as entirely AI?

  4. September 13, 2026
    1. 🔗 gildas-lormeau/single-file-cli v2.12.0 release

      SingleFile CLI 2.12.0

      CLI fixes and improvements

      • --insert-canonical-link turns the canonical link off. The element is still inserted by default, so nothing changes unless you ask for it. The option existed in the core library but was forced on after the options were merged, so passing it did nothing at all
      • --read-maff-metadata saves the original URL and the archive time of a page that was extracted from a MAFF archive, read from the index.rdf file stored next to it. Without it the page is saved under the path of the extracted copy, dated today. It is off by default because it asks for an index.rdf beside every page saved, which is a request that fails on the open web

      Fixes from single-file-core 1.5.130

      • Saved pages can no longer submit forms or set a base URI. Both were available to a saved page and neither has any use once the page is on disk
      • javascript: URIs are removed from every attribute, in every namespace. The previous pass read the resolved property, so it missed the href of an SVG link and never saw xlink:href
      • Reading the metadata of a page extracted from a MAFF archive no longer fails the capture when index.rdf declares an original URL with no RDF:resource attribute
      • The filename template data of a page extracted from a MAFF archive records the URL recovered from the archive, not the path of the extracted copy

      Changes

      • zip.js is updated to 2.15.0

      Co-authored by Claude (Claude Code)

    2. 🔗 benji.dog rss

      The Matches onstage

      Every few years The Matches reunite for a couple of shows to celebrate the anniversary of one of their albums. For as many times as I've seen them, this was the first time I get to listen to some of the less played songs from Decomposer.

    3. 🔗 r/LocalLLaMA 3k$ 128GB VRAM + 256GB RAM DDR4 Server rss

      3k$ 128GB VRAM + 256GB RAM DDR4 Server | I finished my home inference server. First I tried Lenovo p620 workstation and while it’s a good value overall it pissed me off with a ton of proprietary Lenovo shit to deal with and I return it in the end. Components: 4xV620 - 1400$ 256GB DDR4 RDIMM 2666 - 610$ Huanandzhi D12D - 410$ EPYC 7452 - 170$ PSU ASRock 1600 - 220$ SSD Samsung 970EVO 1tb - Already had Case//Fans//Misc ~ 200$ Power consumption is no shit ofc on such machine: 700-900w prefill
      500-600w decode on Qwen3.8-next-flash Autoround W4A16 What it can do - EDIT: Qwen3.8-next-flash Autoround W4A16 1.3k prefill and 70tg code/60tg prose on 128k+ context with MTP-2 on vllm fork. I was disappointed with this machine and qwen3.8-27b speeds at first. But since Qwen3.8 next running good on it - I’m satisfied. Hope in more optimizations in future. submitted by /u/Thin_Pollution8843
      [link] [comments]
      ---|---

    4. 🔗 persiyanov/herdr-reviewr herdr-reviewr 0.37.1 release

      Changed

      • Base picker. Every branch is a row with its age and role (pr base, default, current),
        the filter is fuzzy, and a typed revision is one more row. Picking the default clears the pick.

      • A repo with no remote has a base. init.defaultBranch, main, or master stands in for origin/HEAD.

    5. 🔗 persiyanov/herdr-reviewr herdr-reviewr 0.37.0 release

      Added

      • A collapsedAll files folder with a changed file under it shows a dot. A collapsed tree
        tells you which folders to open.
    6. 🔗 r/LocalLLaMA The Local LLM community feels like the golden era of the internet all over again rss

      Lately because of the current hardware shortage, unfortunately or fortunately, we can’t just throw infinite cloud compute at our problems, but we’re forced to actually care about what’s happening under the hood. We’re tweaking inference engines, learning quantization math, and optimizing architecture just to squeeze as much performance as possible for the lowest possible setups.

      Fact: Just recently, the forked llama.cpp(s) and halogen-flash- server of Strix Halo pushed the performance through the roof, achieving double performance in decode (52tok/s), 5-6x performance in prefill (1300tok/s) for Qwen 3.8 Flash Next (Q38FN), and Q38FN itself is another massive architecture improvement with Engram, making it not only small but also smart.

      I still remember before the hardware shortage, as someone who loves tweaking and optimizing, people just told me to stop, tweaking is stupid, just buy more RAM, buy more GPU..

      It reminds me of the early web.. Back when setting up a box or hosting a server meant digging through forum threads, troubleshooting on IRC, and freely sharing custom scripts just to make things work. That era didn’t just produce programmers; it built hyper-versatile, end-to-end thinkers who understood the stack from bare metal up.

      Contrast that with where mainstream web culture ended up. Most platforms today like Tiktok, Facebook, Youtube... are engineered for zero-friction doomscrolling.. Endless feeds of short-form videos designed to keep us distracted and waste our time. We’ve been overpampered by convenience.

      My point: When we have too little, we try to learn more. When we have too much, we get distracted and learn too little. This is the golden time of our Local LLM community, let's learn and improve!

      submitted by /u/feelspeaceman
      [link] [comments]

    7. 🔗 MetaBrainz Picard 3 Release Candidate 3 rss

      Today the Picard team is making available a third release candidate for Picard 3. We received good feedback on the previous release candidate 2, thanks to everyone for testing and providing feedback. The final 3.0 release is planned to happen in a few weeks.

      Please test, test, and test , report any issue on forums, matrix, or, ideally, on the ticket system.

      When reporting an issue, always provide details about your environment and a full debug log helps us a lot.
      Also that's (always) a good time to review and improve translations.

      Download links and a detailed list of changes since Picard 3 release candidate 2 are available below. For a more detailed overview of what is new in Picard 3 please see the previous blog post Picard 3 Alpha Release.

      While we have all the major features implemented and with the latest bug fixes we are confident in the current code, this is still a pre-release and there might be bugs. If you use this, do so with care, backup your files and please report any issues you encounter.

      If you are updating from Picard 2, note that some of the changes are backward incompatible, hence we recommend you make a backup of your Picard.ini config file before trying this version. You can do so in Picard’s Options under Advanced > Maintenance.

      What’s new?

      Bug

      • [PICARD-3428] - Metadata options page greys out after being opened once when translation_locales contains an unknown locale code
      • [PICARD-3429] - If Picard was built with updates check disabled the update level setting must be hidden
      • [PICARD-3430] - macOS: Text color issues in dark mode
      • [PICARD-3432] - Frozen builds do not bundle picard.plugin3.cli, breaking plugins that import it
      • [PICARD-3433] - Options page: field highlight and bottom warning disagree about profile overrides
      • [PICARD-3436] - Profile-overridden settings are only highlighted on the first Options page shown
      • [PICARD-3437] - Importing a shareable profile that contains tagger scripts leaves the scripts disabled

      Improvement

      • [PICARD-3431] - Plugin install/enable/disable/uninstall errors aren't clearly displayed as coming from the plugin
      • [PICARD-3434] - Use Modern Style for Wizard across all platforms
      • [PICARD-3435] - Setup wizard: add option to convert Unicode punctuation to ASCII

      Download

      We appreciate your interest in trying this new version. Use with care, backup your files and please use theMetaBrainz community forums and the ticket system to give feedback and report bugs.

      For Windows and macOS you can download the release candidate version from the Picard download page. Linux users can run from source or try the "candidate" channel of the Picard snap package.

      Picard is free software and the source code is available on GitHub.

      Helping out

      We want to provide a polished release of Picard 3.0 that everyone can feel confident to upgrade to. To achieve this, we need the help from the MusicBrainz community.

      The easiest way to help us getting a great Picard 3.0 release is using and testing this release candidate. Please report bugs on the Picard issue tracker and provide feedback in the community forums.

      We also need help with translations, as there are a lot of new features and UI changes over previous Picard versions. We will avoid any further text changes before the final release, so now is the right time to help with translation on Weblate. Please see the translation instructions on the Wiki for details.

      If you are a software developer you are very welcomed to provide fixes and features. Picard is free software and the source code is available on GitHub. See Contributing to Picard on the Picard website to get started.

      You can also look at the new Plugin API and develop plugins for Picard or update your existing Picard 2 plugin to work with Picard 3.

      Acknowledgements

      Code contributions by Bob Swift, Laurent Monin and Philipp Wolfer.
      Translations were updated by BestSteve (Chinese (Traditional Han script)), blueday (Swedish) and Marc Riera (Catalan).

    8. 🔗 HexRaysSA/plugin-repository commits sync repo: +1 release rss
      sync repo: +1 release
      
      ## New releases
      - [DeepExtract](https://github.com/marcosd4h/deepextractida): 0.9.14
      
    9. 🔗 r/LocalLLaMA The Hugging Bay rss

      The Hugging Bay | New website to download models in case HF starts censoring or limiting access. submitted by /u/Thrumpwart
      [link] [comments]
      ---|---

    10. 🔗 Kevin Lynagh Multitouch UI, remote microcontroller flashing, LLM task workflow rss

      Hi friends,

      I've been focused on a few projects I'm not quite ready to discuss yet, so this issue is a bit of a smorgasbord of thoughts and vibe-coded tool releases:

      • Musings on multi-touch: After 20 years, why is it still just zooming/scrolling?
      • Probetron: Turning a Raspberry Pi 4b into a network-accessible microcontroller programmer/debugger
      • LLM task workflow harness

      Also, I'll be in:

      • New York City, Sept 26-Oct 6
      • Milan, Oct 6-8

      Let me know if you want to hang or have any favorite food/activity recommendations!

      Why not more multitouch?

      Apple popularized multitouch input with the iPhone and Mac trackpads in the late '00s, and since then it seems like most programs still only take advantage of the multitouch input for passive navigation: vertical scrolling, horizontal panning, and zooming.

      Multitouch gestures are certainly well suited to these uses; in comparison, I feel extremely slow and clumsy whenever I have to zip around a PDF document or 2D canvas using only a mouse/trackball while holding down control/shift/command/who-can-even-remember.

      However, I'm surprised that so little software seems to take advantage of such high-bandwidth human control input. In researching this topic I came across BetterTouchTool and was floored by the enormous number of gestures and input modalities it supported out of the box:

      I'm now using it to map various taps and touches to commands like move/duplicate/rotate in my CAD tool, so that I can keep my hands on the trackpad rather than constantly running them back and forth to press hotkeys on the keyboard.

      While it's certainly fun to map a rotation of my fingers to the "r" key to trigger a "rotate" command in KiCad, it has me thinking of just what might be possible if multitouch were actually designed into an application rather than "bolted-on" like this to existing key shortcuts.

      I'm sure iPad apps have a lot more sophisticated touch handling, though the locked-down, consumption-oriented focus of that device has kept me away from it. The two apps I've noticed as an outsider to the platform are:

      • Concepts (having only seen it in use via Stuff Made Here's engineering videos)
      • Shapr3d, a legit Parasolid-backed CAD program on the iPad supporting touch and pencil

      I've heard there are plenty of "Professional" apps for the iPad -- let me know if you're using any that have sophisticated multitouch or pen input for creative work!

      Recently I've been designing PCBs using KiCad, and the experience of trying to place and connect hundreds of objects by tediously clicking on them one-by-one and nudging them around with arrow keys or a single cursor has me dreaming of what a multi-touch-forward interface might look like. What sorts of operations might we have for making selections, refining them, and manipulating the underlying entities?

      Four years ago I experimented with CADtron, a pen/mouse-gesture-first 2D geometric CAD, and I'm starting to get the itch again. Especially now with LLMs doing the grunt work, it feels possible to build something more than just a research prototype…

      Probetron

      Speaking of LLMs and designing circuit boards, I recently found myself shuttling a microcontroller between:

      • my desktop computer, where I was having an LLM generate firmware, and
      • my lab bench, where I was connecting it to an apparatus

      After a few iterations of this -- asking the LLM to generate some firmware, flashing it, walking it over and reconnecting on the bench, noting the error message, relaying that back to the LLM, repeat -- I realized I'd become what my friend calls a "reverse centaur". Where a regular centaur combines the strength and speed of a horse body with the intelligence of a human head, the reverse centaur combines the feeble body of a person with the skittish, doofy mind of a horse -- the worst of both worlds.

      Upon realizing this, I threw together a tool for flashing a microcontroller and forwarding input/output (SWD, UART, and USB serial) over the network. This would allow me to put "hardware-in-the-loop" as they say, hand everything to an LLM, and take myself out of the inner "is the well-specified feature actually working?" iteration loop.

      I built everything around a Raspberry Pi 4b, as I had one lying in a drawer. The device-under-test (DUT) can be flashed and reset using the Pi's GPIOs, so no additional programmer hardware is needed:

      A Raspberry Pi 4b with a mess of wires connecting it to an rp2350
microcontroller

      My initial "this'll take an hour for an LLM to throw together" estimate turned out to be a bit off -- the project took the better part of a weekend. Partly scope creep:

      • baking an immutable SD card OS image from within a Linux VM rather than provisioning software on the Pi directly
      • refusing to require any configuration/setup on client machines, which, when combined with SSH really not wanting you to have keyless/passwordless login, means a lot of shenanigans serving an SSH private key over HTTP (lol)

      but mostly because lots of lil' rough edges came up during my initial usage:

      • the probe.rs progress bar doesn't use newlines, but instead relies on learning your terminal's width so that your terminal actually wraps the lines (and thus the script needs to forward your terminal width to the Pi)
      • when the Pi wasn't coming up on the network, since I didn't have an extra monitor handy to debug it, the LLM recommended adding "usb gadget mode" to the image so that when plugged into another computer via USB-C, the Pi would identify as a network adapter and thus you could SSH in to debug over a USB cable (that was a pretty cool solution, I thought)
      • there were all sorts of race-conditions related to the DUT's serial-port-over-USB; I exposed the USB serial via a TCP socket so that the programs I'd already developed for my firmware would need only minimal modifications (opening a tcp://... instead of /dev/tty.usbserial123). This was quite glitchy until I realized that my firmware's "hello, thanks for connecting to me" initial message was getting lost in the Pi's OS buffers -- I had to modify the harness on the Pi so that it only opened the usb serial port when a real client connected to the TCP socket (and likewise closed the serial port when the TCP socket closed).

      The code is 100% LLM-generated, but it's working well enough in my use case flashing rp2350 microcontrollers and forwarding their USB serial output, that it's likely useful to others as well. I've open-sourced the repo here, give it a spin and let me know how it goes!

      A single-file LLM task workflow harness

      Back in May, I wrote:

      No matter how much you plead in markdown:

      You MUST run test.sh before committing

      there's a chance they'll just go ahead and commit anyway (or "fix" the failing test by deleting it, etc.).

      If you want LLMs to follow a deterministic process, you must use them via a deterministic harness.

      Beyond simply ensuring tests/linters are run, another trick I've found that improves LLM code output is running a fresh context with a generic prompt like "review the last commit and tidy up any duplicated code, verbose comments, etc.". I found it pretty funny that even a frontier model like Fable 5 reliably makes a mess when it's implementing anything, such that running the same model in a fresh clean up context yields a transcript full of gems like "oh, this last commit added the same block of code in four places, I should make this a reusable function!"

      While there are tons of harnesses out there, I wanted one that's small and human-readable. For fun, I made it a single Babashka file so it'd be totally self-contained and easy to copy, modify, etc.

      I'll first talk about the workflow, then about the implementation details in Clojure and my dissatisfaction with the explicit state-machine architecture.

      Task workflow

      For the workflow itself, I was inspired by my friend Colin's pi- task, in particular how it "frontloads" human involvement: One starts by interactively discussing the task scope with LLM first, then factoring that into a plan, which is then implemented autonomously.

      I really enjoy the interactive design session, which is substantially more comprehensive than the "plan mode" built into Claude Code and Codex (which only seem to ask me a few clarification questions at best before trying to jump into implementation).

      My harness follows Colin's by starting with separate steps (context + prompt) for:

      • discussing the scope of the task itself ("refinement"),
      • how to implement that scope in terms of individually deliverable/testable subtasks ("planning"),
      • reviewing the plan

      The prompts for these steps encourage the LLM to ask a single multiple choice question at a time, but since the responses are free-form text it's always easy to steer the conversation:

      • none of these are good options, what about direction X?
      • let's not do this functionality at all
      • this is a prototype, don't worry about that edge case
      • clone project A and see how they handled this

      After the task has been refined, the fresh context of the planning step divides the (now detailed) task specification into explicit subtasks. Each subtask can have:

      • explicit dependencies (on other subtasks)
      • check scripts that must pass before the subtask can be committed

      To accomplish task X, the subtasks might be something like:

      • refactor the existing functionality spread across the codebase into a new crate/name
      • add new namespace with additional supporting functionality
      • implement X using these two new namespaces

      I much prefer steering not-so-clever implementation agents upfront, rather than having a "frontier long task horizon" agent get, uh, creative with a gazillion tokens.

      Finally, the "plan review" step launches a fresh context to review the generated plan. This has the same free-form Q&A format discussed above, and usually finds a handful of places where the subtask implementation or testing details are unclear.

      This last step can be repeated as many times as desired. Furthermore, I designed the workflow to emit task.md as a sort of "structured markdown":

      # My task
      
      check: tests-run-for-every-subtask.sh
      
      Some background context that's given to every implementing subtask
      
      ## Subtasks
      
      ### a
      
      some subtask
      
      ### b
      
      dependencies: a
      
      another subtask
      
      ### c
      
      check: specific-test-only-for-this-subtask.sh
      
      yet another subtask
      

      so at any point you can decide to "take matters into your own hands" and edit the plan/subtasks directly rather than try to explain it to the LLM.

      I tend to spend about 30-60 minutes in these authoring steps, which yields a task.md overview that is much more detailed and comprehensive than anything I'd have come up with on my own in the same period of time. A test-plan.md is also generated, which is intended to help you walk through and test that the task was done properly -- it contains stuff like user interface and hardware tests that the LLM can't do on its own as part of the implementation.

      Once you're happy with the plan, run tasktron.clj approve and the harness will:

      1. create a new branch for the task
      2. start subtask implementations in parallel, using git worktrees

      Each subtask implementation agent is prompted "Do just [subtask description] as part of [task description]". When the implementation agent completes, a review agent is prompted "Review this commit for conformance to this task and subtask" and can decide to:

      • approve the code unchanged
      • approve the code with amendments
      • reject the code and restart the subtask with a fresh implementation context with additional instructions

      When approved, the harness handles cherry-picking the commit onto the task branch tip. (If there's a conflict, an agent is started to handle it.)

      While the task is cooking, a status overview is displayed:

      Once everything has been completed, I review the work and merge the branch myself. I tend to do non-fast-forward merges so it's clear in the git history that some commits were done as part of a single conceptual task. As part of the merge, I also check-in the task.md and test-plan.md so that context is stored in the repository.

      I've been using this workflow for about a month and I'm quite happy with it thus far. The authoring workflow in particular has been awesome, and it has definitely helped me come up with better designs than I would've otherwise. I'm also happy with the local-first workflow where everything is built using git branches and worktrees (rather than some remote issue tracking API).

      If you want to give it a spin, put tasktron.clj on your path, run it in a git repository, and follow the instructions. It shells out to pi and/or claude, and you can edit the source to select the harness and model for the specific workflow step. (I can't stand talking to Claude but have free tokens, so I discuss with GPT-5.6-Sol and have Opus 4.8 implement.)

      Task harness implementation

      One of my goals was to implement the task harness as a single, "obviously correct" file, which could be read from the top down in a sort of "bottom-line up front" fashion, with the overall architecture coming first and the grittier implementation details coming later. (See Grant Slatton's How to write complex software for more on this approach.)

      I wrote it in Clojure, as that's a concise, data-oriented language I know well.

      Finally, I wanted the harness to be robust, with all of the essential state stored on disk, so that after an LLM provider outage, power outage, etc., I could just run tasktron.clj again and it would continue exactly where it left off (ideally resuming the in-flight LLM sessions by their transcript UUID in the same worktrees).

      To do this, I implemented the system as an explicit state machine, using Malli to make legible the expected data shapes.

      For example, each subtask state is associated with some data and possible transitions to other states:

      (def state->definition
        {:initial               {:schema      :map
                                 :transitions {:specified :pending}}
      
         :pending               {:schema      :map
                                 :transitions {:implementation-started :implementing}}
      
         :implementing          {:schema      [:map [:base CommitId]]
                                 :transitions {:implementation-finished :checking
                                               :blocked                 :blocked}}
      
         :checking              {:schema      [:map [:base CommitId] [:commit CommitId]]
                                 :transitions {:check-passed :reviewing
                                               :check-failed :revising
                                               :blocked      :blocked}}
      
         :reviewing             {:schema      [:map [:base CommitId] [:commit CommitId]]
                                 :transitions {:feedback :revising
                                               :amended  :checking-amendment
                                               :approved :awaiting-integration
                                               :blocked  :blocked}}
      
         ...})
      

      The events associated with each subtask are stored in an append-only log on disk, from which the current state is derived. The next state is derived from the current state and some event, usually the result of an agent turn, but which might also come from the harness itself (running tests, reporting an infrastructure failure, etc.).

      Effects are reified as data so that side-effects can be isolated to a single function and the vast majority of the code and tests can remain functionally pure (i.e., they don't have side-effects and they return values determined entirely by the provided arguments). The effects themselves are derived from the state; e.g., if the next state is "attempt-rebase" the associated effect contains the commit ID of the source and the commit ID of the target (that we're trying to rebase onto), with the effect handler returning an event (either "success, here's the new commit ID" or "there's a conflict").

      The core interpreter loop of the harness is then fairly compact:

      (loop [state                   initial-state
             subtask                 nil
             event                   initial-event
             subtask->running-effect {}]
        (let [observed-task   (parse-task (slurp (str (fs/path task-dir TASK-FILENAME))))
              state           (reconcile-task state observed-task)
              next-state      (step state subtask event (now))
              effects-desired (effects-for-state next-state)
              effects-pending (remove (fn [{:keys [subtask]}]
                                        (contains? subtask->running-effect subtask))
                                      effects-desired)]
      
          ;; Write state before running effects so re-running recovers from crashes.
          (save! task-dir next-state)
          (report! next-state (now))
      
          (let [subtask->running-effect (reduce (fn [active {:keys [subtask] :as effect}]
                                                  (submit-effect! executor completed worktrees runtime effect)
                                                  (assoc active subtask effect))
                                                subtask->running-effect
                                                effects-pending)]
            (if (seq subtask->running-effect)
              (let [{:keys [subtask event]} (await-completion! completed interactive? report! next-state)]
                (recur next-state
                       subtask
                       event
                       (dissoc subtask->running-effect subtask)))
              next-state))))
      

      All-in-all, the harness consists of:

      • 500 lines of prompts and Malli schemas
      • 1500 lines of domain-specific workflow and task interpreter code
      • 500 lines of supporting code for parsing/generating markdown, interacting with Git, and handling CLI arguments
      • 2000 lines of LLM generated tests

      While it works well enough, I don't feel like the code meets my goal of being "obviously correct".

      All the code reifying effects, managing an explicit state machine, and deriving state from an immutable log all obscure the core subtask workflow:

      • create/reset the worktree to the latest branch tip
      • invoke implementation agent on the subtask
      • run the check script(s)
      • invoke review agent, having it either accept, amend, or send back to re-implement from scratch
      • run the check script(s) again (if amended)
      • allow up to 3 retries of this loop (more than that indicates the subtask is ill-specified)
      • cherry-pick onto the branch tip

      I've been quite happy with this core workflow, and actually want to extend the harness with a "one-shot" entry point that kicks off this workflow for a single prompt (skipping the full refinement and plan steps), for those cases where I'm working on something and notice a minor thing that could be fixed/improved that I can review later when I'm finished with my current work.

      Before I add new features to the harness, though, I'd like to refactor away from the explicit state machine design to reduce the amount of code.

      I'm thinking the "durable execution" pattern might be a good fit. Essentially, memoize (durably, on disk) every side-effecting function call such that on failure/restart the system automatically "replays" itself back into the same state without re-executing side-effects that've already run.

      Most of what I've found in this space (e.g. Temporal, Armin Ronacher's Absurd Workflow) rely on some external database service, which is antithetical to my "it's a single readable script" goal. I'm curious to see how concisely everything could be hand-rolled in Clojure, but of course if you are aware of relevant prior art (in any language) please let me know!

      Misc. stuff

    11. 🔗 Ampcode News Free Agent rss

      Amp is now free to use when you bring your own compute and model subscriptions/keys.

      • No monthly plan is required anymore to use your ChatGPT subscription with Amp.
      • No BYOK token fees or limits for anyone (except our Enterprise tier).

      You pay us for orbs, our remote computers where your agents run independently and in parallel. Or you can use runners on your own computers to run your Amp agents for free.

      You can still pay for model inference through Amp if you want, with no markup.

      Here's our new free Hobby tier and no-extra-charge Teams tier, which make it easier to get started with Amp and to get your whole team on it:

      See Pricing for full details, but let's walk through what changes and what doesn't.

      For Amp Megawatt & Gigawatt Members

      On average, Megawatt members get a 60% discount and Gigawatt members get a 65% discount on their Amp usage. It's a great deal.

      You'll keep getting the same heavily discounted orb usage and model usage each month, plus you can now feed your orbs with tokens from more places.

      With this change, you're no longer effectively bound by the limits of your ChatGPT sub. You can tap other model providers or your company's preferred AI gateway/provider (more on that below), all without any Amp token fees or limits.

      For Teams

      We've eliminated the biggest sources of friction you might hit when spreading Amp, cost-wise and security-wise.

      Now, you can invite everyone to your Amp workspace for free and feel the magic of Amp's shared threads, shared portals, and multiplayer.

      Everyone can use Amp with ChatGPT subs or your company's preferred AI gateway via BYOK, with no monthly fees.

      Power users can upgrade to paid tiers for big orb discounts, while everyone else in the workspace can remain on the free Hobby tier and consume your pooled workspace credits for orbs pay-as-you-go.

      Also, you get free SAML/OIDC SSO for the whole workspace if at least one member is on a paid tier.

      Finally, we're now formally extending our zero and minimal data retention policy to all Amp users and workspaces, not just to our Enterprise tier. (Everyone was always covered by it for all Amp usage since we started, but until now we could only contractually guarantee that to our Enterprise customers.)

      One More Thing: More BYOK Options

      Today, Amp Megawatt and Gigawatt members are also getting early access to more BYOK providers:

      • OpenRouter
      • Amazon Bedrock
      • Google Cloud Agent Platform (f/k/a Vertex AI)
      • Azure Foundry
      • Vercel AI Gateway
      • Cloudflare AI Gateway
      • Ollama Cloud
      • OpenCode Go
      • Custom endpoint URLs

      To try these:

      1. Enable the More AI Routers & Subscriptions experimental feature.
      2. Configure them in Model Routing in personal settings or workspace settings.

      We'll roll this out to everyone soon.