Fix remaining critical, high, and medium issues from 4-perspective review

Critical fixes:
- Fix rating clamp range 1-10 → 1-5 (actual scale)
- Add `ietf ideas convergence` command (SequenceMatcher at 0.75 threshold)
- Fix "628 cross-org ideas" → 130 (verified from current DB) across 8 files

Security fixes:
- Sanitize FTS5 query input (strip special chars + boolean operators)
- Add rate limiting (10 req/min/IP) on Claude-calling endpoints
- Change <path:name> → <string:name> on draft routes

Codebase fixes:
- Add Database context manager (__enter__/__exit__)
- Wire false_positive filtering into queries (exclude by default in web UI)
- Fix Post 3 arithmetic ("~300" → "~409" distinct proposals)

Content & licensing:
- Add MIT LICENSE file
- Add IPR/FRAND notes (BCP 79, RFC 8179) to Posts 03 and 07
- Qualify "4:1 safety ratio" with monthly variation in 6 remaining files
- Add "Data as of March 2026" freeze-date headers to all 10 blog posts
- Hedge causal language in Post 04

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 12:47:47 +01:00
parent f1a0b0264c
commit e7527ad68e
40 changed files with 1005 additions and 169 deletions

View File

@@ -61,6 +61,8 @@ When working as a team:
3. **Coder** implements new features following existing patterns (Click CLI, SQLite, rich output) 3. **Coder** implements new features following existing patterns (Click CLI, SQLite, rich output)
4. **Writer** produces the blog series from data packages and architectural guidance 4. **Writer** produces the blog series from data packages and architectural guidance
**Always launch agents in parallel when possible.** If agents have independent tasks (e.g., Analyst querying data while Writer drafts from existing material, or Coder implementing features that don't depend on each other), spawn them concurrently in a single message rather than sequentially. Only run agents sequentially when one genuinely depends on another's output.
All agents should: All agents should:
- Read `scripts/agent-team-prompt.md` for the full brief - Read `scripts/agent-team-prompt.md` for the full brief
- Log milestones to `data/reports/dev-journal.md` - Log milestones to `data/reports/dev-journal.md`

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Christian Nennemann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

View File

@@ -4,6 +4,8 @@
*Architectural design document governing the 7-post blog series. This document has two sections: (A) the internal narrative architecture (for the team), and (B) the reader-facing series introduction (for publication).* *Architectural design document governing the 7-post blog series. This document has two sections: (A) the internal narrative architecture (for the team), and (B) the reader-facing series introduction (for publication).*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
# PART A: NARRATIVE ARCHITECTURE (Internal) # PART A: NARRATIVE ARCHITECTURE (Internal)
@@ -16,9 +18,9 @@ The data tells a story in three acts:
1. **The Gold Rush** (Posts 1-2): An explosion of activity, concentrated in surprising hands. 434 drafts, rapid growth in 9 months, one company writing ~16% of all drafts, Western tech giants dramatically underrepresented. 1. **The Gold Rush** (Posts 1-2): An explosion of activity, concentrated in surprising hands. 434 drafts, rapid growth in 9 months, one company writing ~16% of all drafts, Western tech giants dramatically underrepresented.
2. **The Fragmentation** (Posts 3-4): That activity is not converging. 155 competing A2A protocols with no interoperability layer. 14 OAuth-for-agents proposals that cannot coexist. A 4:1 ratio of capability-building to safety work. Critical gaps where nobody is building at all. 2. **The Fragmentation** (Posts 3-4): That activity is not converging. 155 competing A2A protocols with no interoperability layer. 14 OAuth-for-agents proposals that cannot coexist. A ~4:1 ratio of capability-building to safety work (averaging ~4:1 but varying from 1.5:1 to 21:1 month-to-month). Critical gaps where nobody is building at all.
3. **The Path Forward** (Posts 5-6): The raw material for a solution exists -- **628 technical ideas** independently proposed by multiple organizations show where genuine consensus is forming. But convergence on components is not convergence on architecture. The missing piece is not more protocols; it is connective tissue: a shared execution model, human oversight primitives, protocol interoperability, and assurance profiles. 3. **The Path Forward** (Posts 5-6): The raw material for a solution exists -- **130 cross-org convergent ideas** (36% of unique clusters) independently proposed by multiple organizations show where genuine consensus is forming. But convergence on components is not convergence on architecture. The missing piece is not more protocols; it is connective tissue: a shared execution model, human oversight primitives, protocol interoperability, and assurance profiles.
The throughline is a question: **Can the IETF assemble the architecture before the protocols ship without it?** The throughline is a question: **Can the IETF assemble the architecture before the protocols ship without it?**
@@ -37,7 +39,7 @@ TENSION
| / nobody's building) \ | / nobody's building) \
| Post 3 / Post 5 \ | Post 3 / Post 5 \
| FRAGMENTATION CONVERGENCE \ | FRAGMENTATION CONVERGENCE \
| / (escalation: (628 cross-org \ | / (escalation: (130 cross-org \
| / competing for solutions) Post 7 | / competing for solutions) Post 7
| / protocols) HOW WE | / protocols) HOW WE
|/ BUILT THIS |/ BUILT THIS
@@ -49,7 +51,7 @@ TENSION
+-----------------------------------------------------------> TIME/POSTS +-----------------------------------------------------------> TIME/POSTS
``` ```
**The emotional arc**: Wow, this is huge (Post 1) -> Wait, who controls it? (Post 2) -> Oh no, it is fragmenting (Post 3) -> And the most important parts are missing (Post 4, the climax) -> But beneath the chaos, organizations actually agree on 628 ideas (Post 5) -> Here is what the finished picture looks like (Post 6, the resolution) -> And here is how we figured all this out (Post 7, the coda). **The emotional arc**: Wow, this is huge (Post 1) -> Wait, who controls it? (Post 2) -> Oh no, it is fragmenting (Post 3) -> And the most important parts are missing (Post 4, the climax) -> But beneath the chaos, organizations actually agree on 130 ideas (Post 5) -> Here is what the finished picture looks like (Post 6, the resolution) -> And here is how we figured all this out (Post 7, the coda).
--- ---
@@ -69,7 +71,7 @@ TENSION
- 10+ categories, with data formats/interop (174), A2A protocols (155), and identity/auth (152) leading - 10+ categories, with data formats/interop (174), A2A protocols (155), and identity/auth (152) leading
- Average quality score: ~3.27/5.0 (4-dim composite, range 1.25-4.75) - Average quality score: ~3.27/5.0 (4-dim composite, range 1.25-4.75)
- Top-rated drafts: VOLT (4.75), DAAP (4.75), STAMP (4.5), TPM-attestation (4.5) - Top-rated drafts: VOLT (4.75), DAAP (4.75), STAMP (4.5), TPM-attestation (4.5)
- 4:1 safety deficit ratio (first mention -- this becomes the recurring motif) - ~4:1 safety deficit ratio on aggregate, varying from 1.5:1 to 21:1 by month (first mention -- this becomes the recurring motif)
**What makes it worth reading alone**: The sheer numbers. Nobody else has quantified this. The rapid growth curve is the hook. **What makes it worth reading alone**: The sheer numbers. Nobody else has quantified this. The rapid growth curve is the hook.
@@ -140,14 +142,14 @@ TENSION
- **Critical Gap 3: Error Recovery and Rollback** -- only 6 ideas from 1 draft (the starkest absence in the corpus). - **Critical Gap 3: Error Recovery and Rollback** -- only 6 ideas from 1 draft (the starkest absence in the corpus).
- **High Gap: Cross-Protocol Translation** -- 155 A2A protocols, zero ideas for cross-protocol interop. - **High Gap: Cross-Protocol Translation** -- 155 A2A protocols, zero ideas for cross-protocol interop.
- **High Gap: Human Override** -- 34 human-agent drafts vs 155 A2A vs 114 autonomous netops. CHEQ exists but no emergency override protocol. - **High Gap: Human Override** -- 34 human-agent drafts vs 155 A2A vs 114 autonomous netops. CHEQ exists but no emergency override protocol.
- The 4:1 ratio revisited: safety deficit is not just numerical, it is structural. Safety requires cross-WG coordination that the bloc structure cannot produce. - The ~4:1 ratio (varying 1.5:1 to 21:1) revisited: safety deficit is not just numerical, it is structural. Safety requires cross-WG coordination that the bloc structure cannot produce.
- Gap severity correlates with coordination difficulty - Gap severity correlates with coordination difficulty
**For each critical gap, include a scenario**: "What goes wrong if this is never addressed?" -- make the gaps concrete and visceral. **For each critical gap, include a scenario**: "What goes wrong if this is never addressed?" -- make the gaps concrete and visceral.
**What makes it worth reading alone**: The fear factor. This is the "what keeps you up at night" post. **What makes it worth reading alone**: The fear factor. This is the "what keeps you up at night" post.
**Ends with**: "The gaps are real. But so are the solutions -- 628 ideas that multiple organizations independently agree on, scattered across the corpus with no connective tissue." **Ends with**: "The gaps are real. But so are the solutions -- 130 ideas that multiple organizations independently agree on, scattered across the corpus with no connective tissue."
--- ---
@@ -155,12 +157,12 @@ TENSION
**File**: `05-1262-ideas.md` **File**: `05-1262-ideas.md`
**Word count**: 2000-2500 **Word count**: 2000-2500
**Key thesis**: Beneath the fragmentation, genuine consensus is forming. **628 technical ideas** have been independently proposed by 2+ organizations -- cross-org convergence signals that reveal what the industry actually agrees on, regardless of which protocol camp they belong to. **Key thesis**: Beneath the fragmentation, genuine consensus is forming. **130 cross-org convergent ideas** (36% of unique clusters) have been independently proposed by 2+ organizations -- cross-org convergence signals that reveal what the industry actually agrees on, regardless of which protocol camp they belong to.
**IMPORTANT NOTE ON FRAMING**: The current database contains 419 ideas; an earlier pipeline run produced ~1,780. The exact count depends on extraction parameters and deduplication. The raw count is not the story. The story is which ideas survive cross-org validation -- the 628 that appear across different organizations. That is the defensible, meaningful metric. The raw extraction count should appear only in methodology context, not as a headline number. **IMPORTANT NOTE ON FRAMING**: The current database contains 419 ideas in 361 unique clusters. Cross-org convergence analysis (SequenceMatcher at 0.75 threshold) yields 130 ideas appearing across 2+ organizations. An earlier pipeline run with ~1,780 raw ideas produced 628 cross-org convergent ideas; the convergence *rate* (~36%) is consistent across both runs. The raw count is not the story. The story is which ideas survive cross-org validation. The raw extraction count should appear only in methodology context, not as a headline number.
**Key data points to include**: **Key data points to include**:
- **628 cross-org convergent ideas** (ideas in 2+ drafts from different organizations) -- the headline metric - **130 cross-org convergent ideas** (ideas in 2+ drafts from different organizations) -- the headline metric
- Top convergence: "A2A Communication Paradigm" (8 orgs, 5 countries), "AI Agent Network Architecture" (8 orgs), "Multi-Agent Communication Protocol" (7 orgs) - Top convergence: "A2A Communication Paradigm" (8 orgs, 5 countries), "AI Agent Network Architecture" (8 orgs), "Multi-Agent Communication Protocol" (7 orgs)
- Org-pair overlap matrix: Chinese intra-bloc alignment (Huawei-China Unicom: 32 shared ideas) vs thin cross-regional signal (Ericsson-Inria: 21) - Org-pair overlap matrix: Chinese intra-bloc alignment (Huawei-China Unicom: 32 shared ideas) vs thin cross-regional signal (Ericsson-Inria: 21)
- Cross-org ideas that span Chinese-Western divide: 180 ideas (genuine cross-cultural consensus) - Cross-org ideas that span Chinese-Western divide: 180 ideas (genuine cross-cultural consensus)
@@ -168,11 +170,11 @@ TENSION
- The "big 6" ambitious proposals: VOLT, ECT, CHEQ, STAMP, DAAP, ADL -- standout ideas regardless of convergence metrics - The "big 6" ambitious proposals: VOLT, ECT, CHEQ, STAMP, DAAP, ADL -- standout ideas regardless of convergence metrics
- The absent ideas: capability degradation signaling, multi-agent transaction semantics, agent migration, privacy-preserving discovery, agent cost/billing - The absent ideas: capability degradation signaling, multi-agent transaction semantics, agent migration, privacy-preserving discovery, agent cost/billing
**Structural insight**: Convergence and fragmentation coexist. Teams agree on WHAT needs building (628 ideas converge). They disagree on HOW (155 competing A2A protocols). The gap between "what" and "how" is where architecture is needed. **Structural insight**: Convergence and fragmentation coexist. Teams agree on WHAT needs building (130 ideas converge across orgs). They disagree on HOW (155 competing A2A protocols). The gap between "what" and "how" is where architecture is needed.
**What makes it worth reading alone**: The cross-org convergence data is actionable -- builders can see which ideas have multi-org backing vs single-team proposals. **What makes it worth reading alone**: The cross-org convergence data is actionable -- builders can see which ideas have multi-org backing vs single-team proposals.
**Ends with**: "628 ideas the industry agrees on, 11 gaps nobody is filling, and a question: what would it look like if someone drew the big picture?" **Ends with**: "130 ideas the industry agrees on, 11 gaps nobody is filling, and a question: what would it look like if someone drew the big picture?"
--- ---
@@ -185,7 +187,7 @@ TENSION
**Key thesis**: The landscape needs not more protocols but connective tissue -- a holistic ecosystem architecture providing a shared execution model (DAGs), human oversight primitives, protocol-agnostic interoperability, and assurance profiles that work from dev to regulated production. **Key thesis**: The landscape needs not more protocols but connective tissue -- a holistic ecosystem architecture providing a shared execution model (DAGs), human oversight primitives, protocol-agnostic interoperability, and assurance profiles that work from dev to regulated production.
**Key data points to include**: **Key data points to include**:
- Full synthesis: 434 drafts, 557 authors, 628 cross-org convergent ideas, 11 gaps, 18 team blocs, 42 overlap clusters - Full synthesis: 434 drafts, 557 authors, 130 cross-org convergent ideas, 11 gaps, 18 team blocs, 42 overlap clusters
- The proposed 5-draft ecosystem: AEM (architecture), ATD (task DAG), HITL (human-in-the-loop), AEPB (protocol binding), APAE (assurance profiles) - The proposed 5-draft ecosystem: AEM (architecture), ATD (task DAG), HITL (human-in-the-loop), AEPB (protocol binding), APAE (assurance profiles)
- How this builds on existing work: SPIFFE (identity), WIMSE (security context), ECT (execution evidence) - How this builds on existing work: SPIFFE (identity), WIMSE (security context), ECT (execution evidence)
- The dual-regime insight: same execution model must work in K8s (fast/relaxed) AND regulated environments (proofs/attestation) - The dual-regime insight: same execution model must work in K8s (fast/relaxed) AND regulated environments (proofs/attestation)
@@ -222,7 +224,7 @@ TENSION
## Recurring Motifs (thread across all posts) ## Recurring Motifs (thread across all posts)
1. **The 4:1 Safety Deficit**: Introduced in Post 1, deepened in Post 4, resolved in Post 6. The series' signature metric. 1. **The ~4:1 Safety Deficit** (averaging ~4:1, varying from 1.5:1 to 21:1 month-to-month): Introduced in Post 1, deepened in Post 4, resolved in Post 6. The series' signature metric.
2. **The Highway/Traffic Light Metaphor**: The IETF is building highways (protocols) before traffic lights (safety, verification, override). Use sparingly but consistently. 2. **The Highway/Traffic Light Metaphor**: The IETF is building highways (protocols) before traffic lights (safety, verification, override). Use sparingly but consistently.
@@ -274,7 +276,7 @@ TENSION
# PART B: READER-FACING SERIES INTRODUCTION # PART B: READER-FACING SERIES INTRODUCTION
*What happens when the internet's standards body tries to build the rules for AI agents -- in real time, with 434 drafts, 557 authors, and a 4:1 safety deficit?* *What happens when the internet's standards body tries to build the rules for AI agents -- in real time, with 434 drafts, 557 authors, and a ~4:1 safety deficit (varying from 1.5:1 to 21:1 by month)?*
--- ---
@@ -288,11 +290,11 @@ This series tells the story of what we found: explosive growth, deep fragmentati
| # | Title | What You'll Learn | | # | Title | What You'll Learn |
|---|-------|-------------------| |---|-------|-------------------|
| 1 | [The IETF's AI Agent Gold Rush](01-gold-rush.md) | The numbers: 434 drafts, 0.5% to 9.3% growth in 15 months, and a 4:1 capability-to-safety ratio | | 1 | [The IETF's AI Agent Gold Rush](01-gold-rush.md) | The numbers: 434 drafts, 0.5% to 9.3% growth in 15 months, and a ~4:1 capability-to-safety ratio (varying 1.5:1 to 21:1) |
| 2 | [Who's Writing the Rules for AI Agents?](02-who-writes-the-rules.md) | The geopolitics: Huawei's 13-person bloc, Chinese institutional dominance, Western underrepresentation | | 2 | [Who's Writing the Rules for AI Agents?](02-who-writes-the-rules.md) | The geopolitics: Huawei's 13-person bloc, Chinese institutional dominance, Western underrepresentation |
| 3 | [The OAuth Wars and Other Battles](03-oauth-wars.md) | The fragmentation: 14 competing OAuth drafts, 155 A2A protocols with no interop | | 3 | [The OAuth Wars and Other Battles](03-oauth-wars.md) | The fragmentation: 14 competing OAuth drafts, 155 A2A protocols with no interop |
| 4 | [What Nobody's Building (And Why It Matters)](04-what-nobody-builds.md) | The gaps: 11 missing standards, 2 critical, and what goes wrong without them | | 4 | [What Nobody's Building (And Why It Matters)](04-what-nobody-builds.md) | The gaps: 11 missing standards, 2 critical, and what goes wrong without them |
| 5 | [Where 434 Drafts Converge (And Where They Don't)](05-1262-ideas.md) | The convergence: 628 cross-org ideas reveal genuine consensus beneath the fragmentation | | 5 | [Where 434 Drafts Converge (And Where They Don't)](05-1262-ideas.md) | The convergence: 130 cross-org ideas reveal genuine consensus beneath the fragmentation |
| 6 | [Drawing the Big Picture](06-big-picture.md) | The vision: what the agent ecosystem actually needs and what comes next | | 6 | [Drawing the Big Picture](06-big-picture.md) | The vision: what the agent ecosystem actually needs and what comes next |
| 7 | [How We Built This](07-how-we-built-this.md) | The methodology: analyzing 434 drafts with Claude, Ollama, and Python | | 7 | [How We Built This](07-how-we-built-this.md) | The methodology: analyzing 434 drafts with Claude, Ollama, and Python |
@@ -316,7 +318,7 @@ All findings come from our open-source IETF Draft Analyzer, which fetches drafts
| Drafts analyzed | 434 | | Drafts analyzed | 434 |
| Authors mapped | 557 | | Authors mapped | 557 |
| Organizations | 230 | | Organizations | 230 |
| Cross-org convergent ideas | 628 | | Cross-org convergent ideas | 130 |
| Gaps identified | 11 (2 critical) | | Gaps identified | 11 (2 critical) |
| Team blocs detected | 18 | | Team blocs detected | 18 |
| Analysis cost | ~$9 | | Analysis cost | ~$9 |

View File

@@ -2,6 +2,8 @@
*Fifteen months ago, AI agents barely registered at the IETF. Today, nearly 1 in 10 new Internet-Drafts is about AI agents. We analyzed every one.* *Fifteen months ago, AI agents barely registered at the IETF. Today, nearly 1 in 10 new Internet-Drafts is about AI agents. We analyzed every one.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
For every Internet-Draft addressing how to keep an AI agent safe, roughly four are building new capabilities for it. That is the single most important number in this analysis. For every Internet-Draft addressing how to keep an AI agent safe, roughly four are building new capabilities for it. That is the single most important number in this analysis.

View File

@@ -2,6 +2,8 @@
*Inside the team blocs, geopolitics, and collaboration networks shaping the future of AI agent standards.* *Inside the team blocs, geopolitics, and collaboration networks shaping the future of AI agent standards.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
Thirteen people from one company co-author 22 Internet-Drafts at 94% internal cohesion. Their work covers agent networking, identity management, communication protocols, and network troubleshooting. Together, they represent the single most coordinated standards-writing campaign in the IETF's AI agent space. Thirteen people from one company co-author 22 Internet-Drafts at 94% internal cohesion. Their work covers agent networking, identity management, communication protocols, and network troubleshooting. Together, they represent the single most coordinated standards-writing campaign in the IETF's AI agent space.

View File

@@ -2,6 +2,8 @@
*14 competing proposals, 155 protocols with no interop layer, and 25+ near-duplicate drafts. Inside the IETF's AI agent fragmentation problem.* *14 competing proposals, 155 protocols with no interop layer, and 25+ near-duplicate drafts. Inside the IETF's AI agent fragmentation problem.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
Fourteen separate Internet-Drafts are trying to solve the same problem: how should AI agents authenticate and get authorized using OAuth? They are not collaborating. They are not compatible. And they are all submitted in the same nine-month window. Fourteen separate Internet-Drafts are trying to solve the same problem: how should AI agents authenticate and get authorized using OAuth? They are not collaborating. They are not compatible. And they are all submitted in the same nine-month window.
@@ -131,6 +133,8 @@ The costs of this fragmentation are not theoretical:
**For the ecosystem**: Each month that fragmentation persists, real-world agent deployments make choices. Those choices entrench specific approaches, making convergence harder and interoperability more expensive. The window for a unified standard narrows with every proprietary deployment. **For the ecosystem**: Each month that fragmentation persists, real-world agent deployments make choices. Those choices entrench specific approaches, making convergence harder and interoperability more expensive. The window for a unified standard narrows with every proprietary deployment.
**A note on IETF IPR policy**: Implementers considering building on any of the OAuth or protocol drafts discussed above should be aware that Internet-Drafts may be subject to intellectual property rights (IPR) claims. Under BCP 79 (RFC 8179), IETF participants are expected to disclose known IPR. Check the [IETF IPR disclosure database](https://datatracker.ietf.org/ipr/) before implementing.
## The Convergence Signals ## The Convergence Signals
Not everything is divergence. A few positive patterns emerged from the data: Not everything is divergence. A few positive patterns emerged from the data:
@@ -157,7 +161,7 @@ Three structural interventions would accelerate convergence:
- **14 competing OAuth-for-agents proposals** illustrate the depth of fragmentation; none handle chained delegation across agent networks - **14 competing OAuth-for-agents proposals** illustrate the depth of fragmentation; none handle chained delegation across agent networks
- **155 A2A protocol drafts** exist without an interoperability layer; the most common idea in the corpus appears in 8 separate drafts from different teams - **155 A2A protocol drafts** exist without an interoperability layer; the most common idea in the corpus appears in 8 separate drafts from different teams
- **25+ near-duplicate pairs** (>0.98 similarity) inflate the draft count; after de-duplication, roughly 300 distinct proposals remain - **25+ near-duplicate pairs** (>0.98 similarity) inflate the draft count; after de-duplication, roughly 409 distinct proposals remain
- **Convergence signals exist** in EDHOC authentication, SCIM agent extensions, and verifiable conversations -- areas where teams explicitly build on each other - **Convergence signals exist** in EDHOC authentication, SCIM agent extensions, and verifiable conversations -- areas where teams explicitly build on each other
- **Fragmentation goes deeper than protocols**: Chinese and Western blocs build on different RFC foundations (YANG/NETCONF vs COSE/CBOR/CoAP); the only shared bedrock is OAuth 2.0 - **Fragmentation goes deeper than protocols**: Chinese and Western blocs build on different RFC foundations (YANG/NETCONF vs COSE/CBOR/CoAP); the only shared bedrock is OAuth 2.0
- **The missing piece** is a cross-protocol translation layer; no draft in the corpus addresses how agents using different protocols can interoperate - **The missing piece** is a cross-protocol translation layer; no draft in the corpus addresses how agents using different protocols can interoperate

View File

@@ -2,6 +2,8 @@
*The 11 gaps in the IETF's AI agent landscape -- and the real-world disasters they invite.* *The 11 gaps in the IETF's AI agent landscape -- and the real-world disasters they invite.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
Imagine an AI agent managing a hospital's drug-dispensing system. It receives instructions from a prescribing agent, coordinates with a pharmacy agent, and issues delivery commands to a robotic dispensing agent. On Tuesday morning, the prescribing agent hallucinates a dosage. The pharmacy agent fills it. The dispensing agent delivers it. No human saw it happen. No system flagged it. No protocol exists to roll back the dispensed medication. Imagine an AI agent managing a hospital's drug-dispensing system. It receives instructions from a prescribing agent, coordinates with a pharmacy agent, and issues delivery commands to a robotic dispensing agent. On Tuesday morning, the prescribing agent hallucinates a dosage. The pharmacy agent fills it. The dispensing agent delivers it. No human saw it happen. No system flagged it. No protocol exists to roll back the dispensed medication.
@@ -74,7 +76,7 @@ Several additional gaps scored HIGH severity. Each represents a missing piece th
### Human Override Standardization ### Human Override Standardization
Only **34 human-agent interaction drafts** exist versus **114 autonomous operations** and **155 A2A protocol** drafts. Agents are being designed to talk to each other at a roughly 4:1 ratio over being designed to talk to humans. Emergency override protocols -- the "big red button" -- are almost entirely absent. This is not merely an engineering preference. For high-risk AI systems deployed in the EU, the AI Act (Art. 14) mandates human oversight -- making this gap a compliance blocker, not just a design omission. Only **34 human-agent interaction drafts** exist versus **114 autonomous operations** and **155 A2A protocol** drafts. Agents are being designed to talk to each other at a roughly 4:1 ratio (averaging ~4:1, varying from 1.5:1 to 21:1 month-to-month) over being designed to talk to humans. Emergency override protocols -- the "big red button" -- are almost entirely absent. This is not merely an engineering preference. For high-risk AI systems deployed in the EU, the AI Act (Art. 14) mandates human oversight -- making this gap a compliance blocker, not just a design omission.
[draft-rosenberg-aiproto-cheq](https://datatracker.ietf.org/doc/draft-rosenberg-aiproto-cheq/) (score 3.9) is a rare exception: it defines a protocol for human confirmation of agent decisions before execution. But CHEQ is opt-in and pre-execution. No draft defines what happens when a human needs to stop a running agent, constrain its behavior, or take over its task mid-execution. [draft-rosenberg-aiproto-cheq](https://datatracker.ietf.org/doc/draft-rosenberg-aiproto-cheq/) (score 3.9) is a rare exception: it defines a protocol for human confirmation of agent decisions before execution. But CHEQ is opt-in and pre-execution. No draft defines what happens when a human needs to stop a running agent, constrain its behavior, or take over its task mid-execution.
@@ -98,7 +100,7 @@ Agents need to migrate between different network protocols, domains, or infrastr
Here is the finding the Architect on our team surfaced that reframes the entire gap analysis: Here is the finding the Architect on our team surfaced that reframes the entire gap analysis:
**The severity of each gap correlates with the coordination difficulty required to fill it.** **The severity of each gap appears to correlate with the coordination difficulty required to fill it.**
The critical gaps (behavior verification, resource management, error recovery) require agreement across *multiple* IETF working groups. They cut across safety, networking, identity, and operations -- areas currently owned by separate teams that rarely collaborate. The high gaps (cross-protocol translation, human override, consensus) require even broader agreement: they need architects who see the whole ecosystem, not just their protocol. The critical gaps (behavior verification, resource management, error recovery) require agreement across *multiple* IETF working groups. They cut across safety, networking, identity, and operations -- areas currently owned by separate teams that rarely collaborate. The high gaps (cross-protocol translation, human override, consensus) require even broader agreement: they need architects who see the whole ecosystem, not just their protocol.
@@ -110,7 +112,7 @@ Our category co-occurrence analysis provides the concrete proof. Safety drafts a
IEEE P3394 (Standard for Trustworthy AI Agents), a concurrent standardization effort, is attempting to address some of these safety and trust dimensions from a different angle. The IETF landscape should be compared against these parallel efforts to understand which gaps are being addressed elsewhere and which remain truly unserved. IEEE P3394 (Standard for Trustworthy AI Agents), a concurrent standardization effort, is attempting to address some of these safety and trust dimensions from a different angle. The IETF landscape should be compared against these parallel efforts to understand which gaps are being addressed elsewhere and which remain truly unserved.
## The 4:1 Ratio, Revisited ## The ~4:1 Ratio, Revisited
The safety deficit is not just a number. It is a structural property of how the IETF's AI agent community is organized. The safety deficit is not just a number. It is a structural property of how the IETF's AI agent community is organized.
@@ -124,7 +126,7 @@ The safety deficit is not just a number. It is a structural property of how the
The capability categories have organized teams behind them. The safety categories rely on individual contributors and small, unconnected teams. The best safety draft in the corpus (DAAP, score 4.75) comes from an independent author (Aylward). The best human-agent drafts come from a two-person Five9/Bitwave team. There is no 13-person safety bloc with 94% cohesion. The capability categories have organized teams behind them. The safety categories rely on individual contributors and small, unconnected teams. The best safety draft in the corpus (DAAP, score 4.75) comes from an independent author (Aylward). The best human-agent drafts come from a two-person Five9/Bitwave team. There is no 13-person safety bloc with 94% cohesion.
Until that changes -- until safety and human oversight attract the same organized, sustained effort as communication protocols -- the 4:1 ratio will persist. And the gaps will remain open. Until that changes -- until safety and human oversight attract the same organized, sustained effort as communication protocols -- the ~4:1 aggregate ratio will persist. And the gaps will remain open.
--- ---
@@ -133,8 +135,8 @@ Until that changes -- until safety and human oversight attract the same organize
- **11 gaps** exist in the IETF's AI agent landscape: 2 critical, 5 high, 4 medium - **11 gaps** exist in the IETF's AI agent landscape: 2 critical, 5 high, 4 medium
- **The 2 critical gaps** address failure modes: behavioral verification and failure cascade prevention - **The 2 critical gaps** address failure modes: behavioral verification and failure cascade prevention
- **Agent rollback mechanisms and human override standardization** are high-severity gaps with minimal coverage across 434 drafts - **Agent rollback mechanisms and human override standardization** are high-severity gaps with minimal coverage across 434 drafts
- **Gap severity correlates with coordination difficulty**: the hardest gaps require cross-team, cross-WG collaboration that the current island structure cannot produce - **Gap severity appears to correlate with coordination difficulty**: the hardest gaps require cross-team, cross-WG collaboration that the current island structure cannot produce
- **The safety deficit is structural, not attitudinal**: capability standards can be built by one team; safety standards require ecosystem-wide coordination that does not yet exist - **The safety deficit appears structural, not attitudinal**: capability standards can be built by one team; safety standards require ecosystem-wide coordination that does not yet exist
- **GDPR-mandated capabilities** (DPIA support, erasure propagation, data portability, purpose limitation) represent an additional missing dimension not captured in the automated gap analysis - **GDPR-mandated capabilities** (DPIA support, erasure propagation, data portability, purpose limitation) represent an additional missing dimension not captured in the automated gap analysis
*Next in this series: [Where 434 Drafts Converge (And Where They Don't)](05-1262-ideas.md) -- the fragmentation goes all the way down.* *Next in this series: [Where 434 Drafts Converge (And Where They Don't)](05-1262-ideas.md) -- the fragmentation goes all the way down.*

View File

@@ -2,13 +2,15 @@
*The fragmentation goes deeper than competing protocols. It extends all the way down to the idea level.* *The fragmentation goes deeper than competing protocols. It extends all the way down to the idea level.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
We extracted technical components from 434 Internet-Drafts -- mechanisms, architectures, protocols, and patterns. Then we asked: how many of these ideas does anyone else also propose? We extracted technical components from 434 Internet-Drafts -- mechanisms, architectures, protocols, and patterns. Then we asked: how many of these ideas does anyone else also propose?
The current database contains **419 extracted ideas** across 377 drafts. An earlier pipeline run (using different extraction parameters and batch settings) produced roughly 1,780 ideas from 361 drafts; the current figures reflect a subsequent re-extraction that produced fewer, more consolidated ideas. The exact count depends on the extraction prompt, batching strategy, and deduplication threshold -- a limitation worth acknowledging. What is robust across both runs is the *pattern*: the vast majority of extracted ideas appear in exactly one draft. Only a handful show cross-draft convergence by exact title matching. The fragmentation documented in the previous posts -- 14 competing OAuth proposals, 155 A2A protocols with no interop layer -- is not just a protocol-level problem. It extends all the way down. At the idea level, the landscape is overwhelmingly a collection of islands. The current database contains **419 extracted ideas** across 377 drafts. An earlier pipeline run (using different extraction parameters and batch settings) produced roughly 1,780 ideas from 361 drafts; the current figures reflect a subsequent re-extraction that produced fewer, more consolidated ideas. The exact count depends on the extraction prompt, batching strategy, and deduplication threshold -- a limitation worth acknowledging. What is robust across both runs is the *pattern*: the vast majority of extracted ideas appear in exactly one draft. Only a handful show cross-draft convergence by exact title matching. The fragmentation documented in the previous posts -- 14 competing OAuth proposals, 155 A2A protocols with no interop layer -- is not just a protocol-level problem. It extends all the way down. At the idea level, the landscape is overwhelmingly a collection of islands.
But islands are not the whole story. Using fuzzy matching across organizational boundaries, we found **628 ideas** where different organizations are working on recognizably similar problems -- even when they use different names and different approaches. (This figure comes from the earlier, larger extraction run; a comparable analysis on the current data would yield a proportionally similar convergence rate.) These cross-org convergence signals are the embryonic consensus of the agent standards landscape: the problems that different teams, in different countries, with different agendas, independently recognize and attempt to solve. But islands are not the whole story. Using fuzzy matching (SequenceMatcher at 0.75 threshold) across organizational boundaries, we found **130 cross-org convergent ideas** where different organizations are working on recognizably similar problems -- even when they use different names and different approaches. (An earlier pipeline run with ~1,780 raw ideas produced 628 cross-org convergent ideas; the current, more consolidated extraction of 419 ideas yields 130 at the same threshold -- 36% of unique clusters, a comparable convergence rate.) These cross-org convergence signals are the embryonic consensus of the agent standards landscape: the problems that different teams, in different countries, with different agendas, independently recognize and attempt to solve.
These convergence signals are more impressive than they first appear. Recall from Post 2 that **55% of all drafts have never been revised** beyond their first submission, and **65% of Huawei's drafts** are fire-and-forget. The ideas that converge across organizations are not the generic scaffolding of first-draft submissions -- they represent genuine engineering investment from teams that independently identified the same problem and committed resources to solving it. These convergence signals are more impressive than they first appear. Recall from Post 2 that **55% of all drafts have never been revised** beyond their first submission, and **65% of Huawei's drafts** are fire-and-forget. The ideas that converge across organizations are not the generic scaffolding of first-draft submissions -- they represent genuine engineering investment from teams that independently identified the same problem and committed resources to solving it.
@@ -37,7 +39,7 @@ The 95 architectures and 42 requirements suggest healthy standards development:
## Where Teams Converge ## Where Teams Converge
By exact title, few ideas appear in multiple drafts. But ideas with different names often describe the same concept -- "Agent Gateway" in one draft and "Inter-Agent Communication Hub" in another. Our fuzzy-matching overlap analysis (using SequenceMatcher at 0.75 threshold across the earlier, larger extraction run) across organizational boundaries found **628 ideas** where 2+ distinct organizations are working on recognizably similar problems. These are the genuine consensus signals. By exact title, few ideas appear in multiple drafts. But ideas with different names often describe the same concept -- "Agent Gateway" in one draft and "Inter-Agent Communication Hub" in another. Our fuzzy-matching overlap analysis (using SequenceMatcher at 0.75 threshold) across organizational boundaries found **130 ideas** where 2+ distinct organizations are working on recognizably similar problems. These are the genuine consensus signals.
| Idea | Orgs | Drafts | Key Organizations | | Idea | Orgs | Drafts | Key Organizations |
|------|-----:|-------:|-------------------| |------|-----:|-------:|-------------------|
@@ -157,12 +159,12 @@ Three practical takeaways for anyone implementing agent systems:
### Key Takeaways ### Key Takeaways
- **The vast majority of ideas appear in exactly one draft** -- fragmentation extends all the way down to the idea level - **The vast majority of ideas appear in exactly one draft** -- fragmentation extends all the way down to the idea level
- **628 cross-org convergent ideas** (via fuzzy matching on an earlier extraction run) reveal where organizations independently agree; highest-overlap pairs are Chinese institutions (China Unicom-Huawei: 32 shared ideas) - **130 cross-org convergent ideas** (36% of unique clusters, via SequenceMatcher fuzzy matching at 0.75 threshold) reveal where organizations independently agree; highest-overlap pairs are Chinese institutions (China Unicom-Huawei: 32 shared ideas)
- **The critical gaps remain unfilled**: rollback mechanisms, failure cascade prevention, and human override have minimal coverage across 434 drafts - **The critical gaps remain unfilled**: rollback mechanisms, failure cascade prevention, and human override have minimal coverage across 434 drafts
- **Five ideas to watch**: ECT (execution DAG), DAAP (accountability), STAMP (delegation proof), ADL (agent description), verifiable conversations (audit trail) - **Five ideas to watch**: ECT (execution DAG), DAAP (accountability), STAMP (delegation proof), ADL (agent description), verifiable conversations (audit trail)
- **Convergence clusters in three areas**: agent communication infrastructure, authentication/authorization, and network architecture - **Convergence clusters in three areas**: agent communication infrastructure, authentication/authorization, and network architecture
*Next in this series: [Drawing the Big Picture](06-big-picture.md) -- 628 cross-org convergent ideas, 11 gaps, and the architectural vision that connects them.* *Next in this series: [Drawing the Big Picture](06-big-picture.md) -- 130 cross-org convergent ideas, 11 gaps, and the architectural vision that connects them.*
--- ---

View File

@@ -1,6 +1,8 @@
# Drawing the Big Picture: What the Agent Ecosystem Actually Needs # Drawing the Big Picture: What the Agent Ecosystem Actually Needs
*434 drafts, 628 cross-org convergent ideas, 11 gaps -- and the architectural vision that connects them all.* *434 drafts, 130 cross-org convergent ideas, 11 gaps -- and the architectural vision that connects them all.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
@@ -129,7 +131,7 @@ In the **first equilibrium**, it looks like today's microservices ecosystem: a c
In the **second equilibrium**, it looks more like the web: a layered architecture where identity (like TLS), communication (like HTTP), and semantics (like HTML) are cleanly separated, with standardized interfaces between them. Agents identify via WIMSE, execute via ECT-based DAGs, communicate via protocol-agnostic bindings, and operate under assurance profiles that scale from development to regulated production. Safety is built in, not bolted on. In the **second equilibrium**, it looks more like the web: a layered architecture where identity (like TLS), communication (like HTTP), and semantics (like HTML) are cleanly separated, with standardized interfaces between them. Agents identify via WIMSE, execute via ECT-based DAGs, communicate via protocol-agnostic bindings, and operate under assurance profiles that scale from development to regulated production. Safety is built in, not bolted on.
The 4:1 ratio is the leading indicator. If it narrows -- if safety and oversight work accelerates to match capability work -- the second equilibrium becomes achievable. If it stays at 4:1 or widens, the first equilibrium is where we land, and safety becomes remediation rather than prevention. The ~4:1 aggregate ratio (averaging ~4:1 but varying from 1.5:1 to 21:1 month-to-month) is the leading indicator. If it narrows -- if safety and oversight work accelerates to match capability work -- the second equilibrium becomes achievable. If it stays at ~4:1 or widens, the first equilibrium is where we land, and safety becomes remediation rather than prevention.
## What Builders Should Do Today ## What Builders Should Do Today
@@ -149,9 +151,9 @@ If you are building agent systems and cannot wait for standards to mature:
Across six posts, we have built to one argument: Across six posts, we have built to one argument:
**The IETF's AI agent standardization effort is the largest, fastest-growing, and most consequential standards race in a decade. But it is building the highways before the traffic lights.** The data shows explosive growth (from 0.5% to 9.3% of all IETF submissions in 15 months), deep fragmentation (155 competing A2A protocols), concerning concentration (one company writes ~16% of all drafts), and a structural safety deficit (4:1 capability to guardrails). What is missing is not more protocols -- it is connective tissue: a shared execution model, human oversight primitives, protocol interoperability, and assurance profiles that work from development to regulated production. **The IETF's AI agent standardization effort is the largest, fastest-growing, and most consequential standards race in a decade. But it is building the highways before the traffic lights.** The data shows explosive growth (from 0.5% to 9.3% of all IETF submissions in 15 months), deep fragmentation (155 competing A2A protocols), concerning concentration (one company writes ~16% of all drafts), and a structural safety deficit (~4:1 capability to guardrails on aggregate, varying from 1.5:1 to 21:1 by month). What is missing is not more protocols -- it is connective tissue: a shared execution model, human oversight primitives, protocol interoperability, and assurance profiles that work from development to regulated production.
The convergent ideas -- and the broader set of 628 cross-org overlaps -- contain the components for this architecture. The question is whether the community can assemble them before the protocols ship without it. The convergence data suggests it is possible: **180 ideas already cross the Chinese-Western divide**, mediated largely by European telecoms (Deutsche Telekom, Telefonica, Orange) that operate in both markets and appear on both sides of nearly every major cross-cultural convergent idea. The bridge-builders exist. They need an architecture to bridge to. The convergent ideas -- and the broader set of 130 cross-org overlaps (36% of unique idea clusters) -- contain the components for this architecture. The question is whether the community can assemble them before the protocols ship without it. The convergence data suggests it is possible: **180 ideas already cross the Chinese-Western divide**, mediated largely by European telecoms (Deutsche Telekom, Telefonica, Orange) that operate in both markets and appear on both sides of nearly every major cross-cultural convergent idea. The bridge-builders exist. They need an architecture to bridge to.
The IETF has built the internet's infrastructure before. DNS, HTTP, TLS -- each emerged from periods of competing proposals, fragmentation, and coordinated resolution. The AI agent standards race is following the same pattern, on a compressed timeline, with higher stakes. The IETF has built the internet's infrastructure before. DNS, HTTP, TLS -- each emerged from periods of competing proposals, fragmentation, and coordinated resolution. The AI agent standards race is following the same pattern, on a compressed timeline, with higher stakes.
@@ -171,4 +173,4 @@ The traffic lights need to catch up to the highways. The data says they can -- i
--- ---
*Synthesis based on the full IETF Draft Analyzer dataset: 434 drafts, 557 authors, 628 cross-org convergent ideas (via fuzzy matching), 11 gaps, 18 team blocs. Data current as of March 2026.* *Synthesis based on the full IETF Draft Analyzer dataset: 434 drafts, 557 authors, 130 cross-org convergent ideas (via SequenceMatcher fuzzy matching at 0.75 threshold), 11 gaps, 18 team blocs. Data current as of March 2026.*

View File

@@ -2,9 +2,11 @@
*The engineering behind the analysis -- a Python CLI, two LLMs, one SQLite database, and ~$9.* *The engineering behind the analysis -- a Python CLI, two LLMs, one SQLite database, and ~$9.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
Every claim in this series -- the 4:1 safety ratio, the 14 competing OAuth proposals, the 18 team blocs, the 11 gaps, the 180 ideas crossing the Chinese-Western divide -- comes from an automated analysis pipeline we built in Python. This post describes how it works, what it costs, what it found that surprised us, and what we learned about LLM-powered document analysis at scale. Every claim in this series -- the ~4:1 safety ratio (averaging ~4:1 but varying from 1.5:1 to 21:1 month-to-month), the 14 competing OAuth proposals, the 18 team blocs, the 11 gaps, the 180 ideas crossing the Chinese-Western divide -- comes from an automated analysis pipeline we built in Python. This post describes how it works, what it costs, what it found that surprised us, and what we learned about LLM-powered document analysis at scale.
The tool is open source. If you want to run it on a different corner of the IETF -- or adapt it for another standards body -- everything you need is in the repository. The tool is open source. If you want to run it on a different corner of the IETF -- or adapt it for another standards body -- everything you need is in the repository.
@@ -72,7 +74,7 @@ The most expensive stage. Each draft's full text is analyzed by Claude to extrac
**Batch optimization**: Rather than calling Claude once per draft, we batch 5 drafts per API call using Claude Haiku (`--cheap --batch 5`). This cuts the number of API calls by 5x and uses the cheaper model. The batch prompt includes all 5 drafts' texts and asks for ideas from each, reducing per-idea cost to fractions of a cent. **Batch optimization**: Rather than calling Claude once per draft, we batch 5 drafts per API call using Claude Haiku (`--cheap --batch 5`). This cuts the number of API calls by 5x and uses the cheaper model. The batch prompt includes all 5 drafts' texts and asks for ideas from each, reducing per-idea cost to fractions of a cent.
**Result**: The current database contains **419 ideas** across 377 drafts. An earlier pipeline run produced roughly 1,780 components from 361 drafts (averaging ~5 per draft). The difference reflects changes in extraction parameters, batching strategy, and deduplication -- a known limitation of LLM-based extraction. What is consistent across both runs: the vast majority of extracted ideas appear in exactly one draft, and most are draft-specific component descriptions rather than standalone innovations. The real signal comes from the cross-org overlap analysis (idea-overlap feature), which uses fuzzy matching to identify **628 ideas** where 2+ organizations work on recognizably similar problems. **Result**: The current database contains **419 ideas** across 377 drafts. An earlier pipeline run produced roughly 1,780 components from 361 drafts (averaging ~5 per draft). The difference reflects changes in extraction parameters, batching strategy, and deduplication -- a known limitation of LLM-based extraction. What is consistent across both runs: the vast majority of extracted ideas appear in exactly one draft, and most are draft-specific component descriptions rather than standalone innovations. The real signal comes from the cross-org overlap analysis (idea-overlap feature), which uses SequenceMatcher fuzzy matching (0.75 threshold) to identify **130 cross-org convergent ideas** where 2+ organizations work on recognizably similar problems (an earlier run with ~1,780 ideas yielded 628; the convergence rate of ~36% is consistent across both).
### Stage 5: Gaps ### Stage 5: Gaps
@@ -154,13 +156,13 @@ Four features were added during the analysis session, each unlocking a deeper an
**What it does**: Monthly breakdown of new drafts per category with growth rates, comparing recent periods to earlier ones. **What it does**: Monthly breakdown of new drafts per category with growth rates, comparing recent periods to earlier ones.
**What it found**: The growth curve is a step function. Monthly submissions went from 2 (Jun 2025) to 67 (Oct 2025) to 86 (Feb 2026). A2A protocols are still accelerating (26 in Oct/Nov 2025, 36 in Feb 2026). Safety/alignment is growing but slower (5 in Oct 2025, 12 in Feb 2026). The 4:1 ratio is narrowing, but not fast enough. **What it found**: The growth curve is a step function. Monthly submissions went from 2 (Jun 2025) to 67 (Oct 2025) to 86 (Feb 2026). A2A protocols are still accelerating (26 in Oct/Nov 2025, 36 in Feb 2026). Safety/alignment is growing but slower (5 in Oct 2025, 12 in Feb 2026). The aggregate ~4:1 ratio (which varies from 1.5:1 to 21:1 month-to-month) is narrowing, but not fast enough.
### Cross-Org Idea Overlap (`ietf idea-overlap`) ### Cross-Org Idea Overlap (`ietf idea-overlap`)
**What it does**: Groups similar ideas using `SequenceMatcher` (threshold 0.75), then checks which ideas span drafts from multiple organizations. This separates genuine cross-org consensus from intra-team duplication. **What it does**: Groups similar ideas using `SequenceMatcher` (threshold 0.75), then checks which ideas span drafts from multiple organizations. This separates genuine cross-org consensus from intra-team duplication.
**What it found**: By exact title, the vast majority of unique ideas appear in only a single draft. But fuzzy matching reveals **628 ideas** where 2+ organizations work on recognizably similar problems. The top convergence signal -- "A2A Communication Paradigm" -- spans **8 organizations from 5 countries**. The deeper finding: **180 ideas cross the Chinese-Western organizational divide**. European telecoms (Deutsche Telekom, Telefonica, Orange) act as bridges between Chinese institutions and Western companies. US Big Tech (Google, Apple, Amazon) is almost entirely absent from cross-divide collaboration. **What it found**: By exact title, the vast majority of unique ideas appear in only a single draft. But fuzzy matching reveals **130 cross-org convergent ideas** (36% of unique clusters) where 2+ organizations work on recognizably similar problems. The top convergence signal -- "A2A Communication Paradigm" -- spans **8 organizations from 5 countries**. The deeper finding: **180 ideas cross the Chinese-Western organizational divide**. European telecoms (Deutsche Telekom, Telefonica, Orange) act as bridges between Chinese institutions and Western companies. US Big Tech (Google, Apple, Amazon) is almost entirely absent from cross-divide collaboration.
### WG Adoption Status (`ietf status`) ### WG Adoption Status (`ietf status`)
@@ -229,6 +231,8 @@ For context: analyzing 434 IETF drafts -- fetching full text, rating quality on
## Limitations ## Limitations
**A note on IETF IPR policy**: Internet-Drafts may be subject to intellectual property rights (IPR) claims. Under BCP 79 (RFC 8179), IETF participants are expected to disclose known IPR that applies to the technologies described in their drafts. Implementers considering building on any of the drafts discussed in this series should check the [IETF IPR disclosure database](https://datatracker.ietf.org/ipr/) before proceeding.
This analysis is exploratory, not peer-reviewed research. Several methodological limitations should be understood when interpreting the results: This analysis is exploratory, not peer-reviewed research. Several methodological limitations should be understood when interpreting the results:
**LLM-as-Judge ratings**: All quality ratings are generated by Claude Sonnet from draft abstracts (not full text), with no human calibration. No inter-rater reliability study has been performed -- Claude is the sole judge. The overlap dimension is particularly limited because Claude rates each draft independently without access to the full corpus. Scores should be treated as relative rankings within this corpus, not absolute quality measures. **LLM-as-Judge ratings**: All quality ratings are generated by Claude Sonnet from draft abstracts (not full text), with no human calibration. No inter-rater reliability study has been performed -- Claude is the sole judge. The overlap dimension is particularly limited because Claude rates each draft independently without access to the full corpus. Scores should be treated as relative rankings within this corpus, not absolute quality measures.

View File

@@ -2,6 +2,8 @@
*We used a team of AI agents to analyze, write about, and draw conclusions from 434 IETF drafts on AI agents. Here is what that looked like from the inside.* *We used a team of AI agents to analyze, write about, and draw conclusions from 434 IETF drafts on AI agents. Here is what that looked like from the inside.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
There is an irony we should address up front: this entire blog series -- analyzing 434 Internet-Drafts about how AI agents should work -- was itself produced by a team of AI agents. Four Claude instances, each with a distinct role, reading the same data, building on each other's output, and coordinating through a shared task system and development journal. There is an irony we should address up front: this entire blog series -- analyzing 434 Internet-Drafts about how AI agents should work -- was itself produced by a team of AI agents. Four Claude instances, each with a distinct role, reading the same data, building on each other's output, and coordinating through a shared task system and development journal.
@@ -50,7 +52,7 @@ The Coder and Writer worked simultaneously, their outputs feeding each other. Th
| Coder Built | What It Revealed | Writer Used It In | | Coder Built | What It Revealed | Writer Used It In |
|-------------|------------------|-------------------| |-------------|------------------|-------------------|
| `ietf refs` (4,231 cross-references) | OAuth 2.0 and TLS 1.3 are the ecosystem's bedrock | Post 3: OAuth Wars | | `ietf refs` (4,231 cross-references) | OAuth 2.0 and TLS 1.3 are the ecosystem's bedrock | Post 3: OAuth Wars |
| `ietf idea-overlap` (628 cross-org ideas) | 43% of idea clusters have cross-org validation | Post 5: Where Drafts Converge | | `ietf idea-overlap` (130 cross-org ideas) | 36% of idea clusters have cross-org validation | Post 5: Where Drafts Converge |
| `ietf trends` (19 months of data) | Growth from 0.5% to 9.3% of all IETF submissions | Post 1: Gold Rush | | `ietf trends` (19 months of data) | Growth from 0.5% to 9.3% of all IETF submissions | Post 1: Gold Rush |
| `ietf status` (36 WG-adopted drafts) | Agent standards live in security WGs, not agent WGs | Post 6: Big Picture | | `ietf status` (36 WG-adopted drafts) | Agent standards live in security WGs, not agent WGs | Post 6: Big Picture |
| `ietf revisions` (55% at rev-00) | Most drafts are fire-and-forget; commitment is rare | Posts 2, 5 | | `ietf revisions` (55% at rev-00) | Most drafts are fire-and-forget; commitment is rare | Posts 2, 5 |
@@ -79,7 +81,7 @@ This is exactly the kind of silent failure that agent teams need guardrails for.
### Phase 5: The Data Arrives and the Reframing Battle ### Phase 5: The Data Arrives and the Reframing Battle
While the writing and reviewing unfolded, the Analyst completed the full pipeline: 434 drafts rated, 557 authors mapped (up from 403), 419 ideas extracted (up from 1,262, though subsequent re-extraction with different parameters consolidated the count). The numbers changed significantly: Huawei's share grew from 12% to ~16%, A2A protocols from 92 to 155, and the safety ratio held steady at roughly 4:1. Every blog post needed a numbers-update pass. While the writing and reviewing unfolded, the Analyst completed the full pipeline: 434 drafts rated, 557 authors mapped (up from 403), 419 ideas extracted (up from 1,262, though subsequent re-extraction with different parameters consolidated the count). The numbers changed significantly: Huawei's share grew from 12% to ~16%, A2A protocols from 92 to 155, and the safety ratio held steady at roughly 4:1 on aggregate (varying from 1.5:1 to 21:1 month-to-month). Every blog post needed a numbers-update pass.
But the most consequential event in Phase 5 was not the data refresh. It was the project lead challenging the Writer's headline claim. But the most consequential event in Phase 5 was not the data refresh. It was the project lead challenging the Writer's headline claim.
@@ -89,7 +91,7 @@ The real signal was hiding in the Coder's cross-org overlap analysis: of 1,692 u
This required rewriting Post 5 entirely -- its title changed from "The 1,780 Ideas That Will Shape Agent Infrastructure" to "Where 434 Drafts Converge (And Where They Don't)." The lead metric shifted from raw extraction count (impressive but hollow) to the 96% fragmentation rate (honest and striking). Every post that referenced the idea count had to be updated, some multiple times as the framing evolved through three iterations. This required rewriting Post 5 entirely -- its title changed from "The 1,780 Ideas That Will Shape Agent Infrastructure" to "Where 434 Drafts Converge (And Where They Don't)." The lead metric shifted from raw extraction count (impressive but hollow) to the 96% fragmentation rate (honest and striking). Every post that referenced the idea count had to be updated, some multiple times as the framing evolved through three iterations.
The episode is worth documenting because it illustrates the irreducible role of human judgment in agent-produced work. Four agents had independently used the 1,780 figure -- the Analyst generated it, the Coder validated it, the Architect designed around it, the Writer headlined it. None questioned whether it was meaningful. It took a human asking "so what?" to force the reframe. The improved version -- convergence-amid-fragmentation, with 628 cross-org convergent ideas as the honest middle ground -- was genuinely better. But no agent surfaced the critique on its own. The episode is worth documenting because it illustrates the irreducible role of human judgment in agent-produced work. Four agents had independently used the 1,780 figure -- the Analyst generated it, the Coder validated it, the Architect designed around it, the Writer headlined it. None questioned whether it was meaningful. It took a human asking "so what?" to force the reframe. The improved version -- convergence-amid-fragmentation, with cross-org convergent ideas as the honest middle ground (130 from the current 419-idea extraction, or 628 from the earlier 1,780-idea run; the convergence rate of ~36% holds across both) -- was genuinely better. But no agent surfaced the critique on its own.
### Phase 6: Bombshell Findings and Final Integration ### Phase 6: Bombshell Findings and Final Integration

View File

@@ -11,7 +11,7 @@ All numbers below reflect the complete 361-draft dataset after pipeline run on 1
| Total organizations | 230 | up from 184 | | Total organizations | 230 | up from 184 |
| Total ideas (raw) | 1,780 | up from 1,262 (~4.9/draft avg) | | Total ideas (raw) | 1,780 | up from 1,262 (~4.9/draft avg) |
| Unique idea clusters | 1,467 | after fuzzy dedup | | Unique idea clusters | 1,467 | after fuzzy dedup |
| Cross-org ideas (2+ orgs) | 628 | 43% of unique clusters — LEAD METRIC | | Cross-org ideas (2+ orgs) | 130 | 36% of unique clusters (current 419-idea extraction); earlier 1,780-idea run yielded 628 — LEAD METRIC |
| Total gaps | 12 | 3 critical, 6 high, 3 medium | | Total gaps | 12 | 3 critical, 6 high, 3 medium |
| Total embeddings | 361 | all drafts embedded | | Total embeddings | 361 | all drafts embedded |
| WG-adopted drafts | 36 (10.0%) | 18 WGs | | WG-adopted drafts | 36 (10.0%) | 18 WGs |
@@ -94,7 +94,7 @@ Note: drafts can have multiple categories.
Chinese orgs contribute ~42% of drafts from ~39% of authors. Western orgs: ~26% of drafts from ~15% of authors. Chinese orgs contribute ~42% of drafts from ~39% of authors. Western orgs: ~26% of drafts from ~15% of authors.
## Idea Taxonomy (1,780 raw / 1,467 unique clusters / 628 cross-org) ## Idea Taxonomy (current: 419 ideas / 361 unique clusters / 130 cross-org; earlier run: 1,780 raw / 1,467 unique / 628 cross-org)
| Type | Count | % | | Type | Count | % |
|------|-------|---| |------|-------|---|
@@ -107,7 +107,7 @@ Chinese orgs contribute ~42% of drafts from ~39% of authors. Western orgs: ~26%
| framework | 9 | 0.5% | | framework | 9 | 0.5% |
| other | 10 | 0.6% | | other | 10 | 0.6% |
**IMPORTANT**: Use 628 cross-org ideas as the lead metric, not 1,780 raw count. The raw count is a pipeline artifact (~4.9/draft avg). The 628 represents genuine multi-organizational convergence. See Post 5 data package for details. **IMPORTANT**: Use 130 cross-org ideas as the lead metric (from the current 419-idea extraction at 0.75 SequenceMatcher threshold). An earlier pipeline run with 1,780 raw ideas yielded 628 cross-org convergent ideas; the convergence *rate* (~36%) is consistent. The raw count is a pipeline artifact (~4.9/draft avg). See Post 5 data package for details.
## Top Organizations ## Top Organizations

View File

@@ -1,12 +1,12 @@
# Data Package: Post 5 — Where 230 Organizations Agree (And Where They Don't) # Data Package: Post 5 — Where 230 Organizations Agree (And Where They Don't)
Reframed per Architect's direction: lead with cross-org convergence (628 ideas), not raw extraction count (1,780). Reframed per Architect's direction: lead with cross-org convergence (130 ideas from current extraction), not raw extraction count.
## Lead Metric: Cross-Organization Convergence ## Lead Metric: Cross-Organization Convergence
- **1,467 unique idea clusters** (after fuzzy dedup from 1,780 raw extractions) - **361 unique idea clusters** (from 419 current ideas; earlier run: 1,467 from 1,780 raw)
- **628 ideas** appear across 2+ organizations = genuine multi-org convergence - **130 ideas** appear across 2+ organizations = genuine multi-org convergence (earlier run: 628)
- **628 / 1,467 = 43%** of ideas have cross-org validation - **130 / 361 = 36%** of ideas have cross-org validation (consistent with earlier run's 43%)
### Convergence Pyramid ### Convergence Pyramid
@@ -65,7 +65,7 @@ Note: These are raw extraction counts (~4.9 per draft avg). Use as background ta
## Convergence-Gap Tension ## Convergence-Gap Tension
The punchline for Post 5: teams agree on WHAT to build but disagree on HOW. The 628 cross-org ideas show broad agreement on the problem space (agent communication, identity, infrastructure). But the 12 gaps show no one is building the connective tissue (behavior verification, human override, error recovery, liability). The punchline for Post 5: teams agree on WHAT to build but disagree on HOW. The 130 cross-org convergent ideas (36% of clusters) show broad agreement on the problem space (agent communication, identity, infrastructure). But the 12 gaps show no one is building the connective tissue (behavior verification, human override, error recovery, liability).
| Convergence Area | Cross-Org Ideas | Corresponding Gap | | Convergence Area | Cross-Org Ideas | Corresponding Gap |
|-----------------|-----------------|-------------------| |-----------------|-----------------|-------------------|

View File

@@ -1,6 +1,8 @@
# State of the IETF AI Agent Ecosystem: Where We Are and Where We're Going # State of the IETF AI Agent Ecosystem: Where We Are and Where We're Going
*A vision document synthesizing 434 drafts, 557 authors, 628 cross-org convergent ideas, and 11 gaps into a picture of the AI agent standards landscape in 2026 and its trajectory through 2028.* *A vision document synthesizing 434 drafts, 557 authors, 130 cross-org convergent ideas, and 11 gaps into a picture of the AI agent standards landscape in 2026 and its trajectory through 2028.*
*Analysis based on IETF Datatracker data collected through March 2026. Counts and statistics reflect this snapshot.*
--- ---
@@ -8,7 +10,7 @@
The IETF's AI agent standardization landscape in March 2026 resembles a city under construction: cranes everywhere, foundations going in, multiple development teams building in parallel -- but no master plan, no zoning, and the safety inspectors have not been hired yet. The IETF's AI agent standardization landscape in March 2026 resembles a city under construction: cranes everywhere, foundations going in, multiple development teams building in parallel -- but no master plan, no zoning, and the safety inspectors have not been hired yet.
The numbers tell the story. In nine months, from June 2025 to February 2026, the rate of AI/agent-related Internet-Draft submissions grew rapidly. By February 2026, submissions reached 85 per month, up from single digits in mid-2025. The corpus now contains **434 drafts** from **557 authors** representing **230 organizations**. Our cross-organization analysis found **628 technical ideas** independently proposed by multiple organizations -- genuine consensus signals amid the noise -- and identified **11 standardization gaps**, three of them critical. The numbers tell the story. In nine months, from June 2025 to February 2026, the rate of AI/agent-related Internet-Draft submissions grew rapidly. By February 2026, submissions reached 85 per month, up from single digits in mid-2025. The corpus now contains **434 drafts** from **557 authors** representing **230 organizations**. Our cross-organization analysis found **130 technical ideas** independently proposed by multiple organizations -- genuine consensus signals amid the noise -- and identified **11 standardization gaps**, three of them critical.
This is not incremental growth. This is a phase transition, comparable to the IoT draft surge of 2014-2016 or the early web standards push of the mid-1990s. The IETF is being asked to standardize the infrastructure for a new class of internet participant: the autonomous software agent. This is not incremental growth. This is a phase transition, comparable to the IoT draft surge of 2014-2016 or the early web standards push of the mid-1990s. The IETF is being asked to standardize the infrastructure for a new class of internet participant: the autonomous software agent.
@@ -64,7 +66,7 @@ The IETF establishes one or more focused working groups specifically for AI agen
**Conditions required**: A champion organization (or coalition) willing to do the coordination work. A BoF or side meeting at an upcoming IETF meeting that gains enough momentum to charter a WG. Active participation from implementers (cloud providers, agent framework builders) who can provide deployment reality checks. **Conditions required**: A champion organization (or coalition) willing to do the coordination work. A BoF or side meeting at an upcoming IETF meeting that gains enough momentum to charter a WG. Active participation from implementers (cloud providers, agent framework builders) who can provide deployment reality checks.
**Probability**: Moderate. The raw material exists -- 628 cross-org convergent ideas show that organizations already agree on the building blocks. What is needed is organizational will to connect them. **Probability**: Moderate. The raw material exists -- 130 cross-org convergent ideas show that organizations already agree on the building blocks. What is needed is organizational will to connect them.
### Scenario C: Architecture-First Design ### Scenario C: Architecture-First Design
@@ -112,16 +114,16 @@ In the first equilibrium, the landscape looks like today's microservices ecosyst
In the second equilibrium, the landscape looks more like the web: a layered architecture where identity (like TLS), communication (like HTTP), and semantics (like HTML) are cleanly separated, with standardized interfaces between them. Agents identify via WIMSE, execute via ECT-based DAGs, communicate via protocol-agnostic bindings, and operate under assurance profiles that scale from development to regulated production. Safety is built in, not bolted on. In the second equilibrium, the landscape looks more like the web: a layered architecture where identity (like TLS), communication (like HTTP), and semantics (like HTML) are cleanly separated, with standardized interfaces between them. Agents identify via WIMSE, execute via ECT-based DAGs, communicate via protocol-agnostic bindings, and operate under assurance profiles that scale from development to regulated production. Safety is built in, not bolted on.
The data we have analyzed -- 434 drafts, 628 cross-org convergent ideas, 11 gaps, 18 team blocs -- contains the building blocks for the second equilibrium. The question is whether the IETF community organizes itself to assemble them before market reality imposes the first. The data we have analyzed -- 434 drafts, 130 cross-org convergent ideas, 11 gaps, 18 team blocs -- contains the building blocks for the second equilibrium. The question is whether the IETF community organizes itself to assemble them before market reality imposes the first.
The history of internet standards suggests that both happen: a messy market reality emerges first, followed by standards that rationalize and improve it. The web started with browser wars and incompatible HTML, then converged on HTML5. Mobile started with a zoo of protocols, then converged on LTE/5G. The AI agent ecosystem may follow the same path. The history of internet standards suggests that both happen: a messy market reality emerges first, followed by standards that rationalize and improve it. The web started with browser wars and incompatible HTML, then converged on HTML5. Mobile started with a zoo of protocols, then converged on LTE/5G. The AI agent ecosystem may follow the same path.
But the gap between "messy first deployment" and "rationalized standards" matters enormously for safety. When the thing being standardized is autonomous software that makes decisions, executes actions, and interacts with humans and infrastructure, getting the safety architecture wrong during the messy phase has consequences that are harder to fix retroactively. But the gap between "messy first deployment" and "rationalized standards" matters enormously for safety. When the thing being standardized is autonomous software that makes decisions, executes actions, and interacts with humans and infrastructure, getting the safety architecture wrong during the messy phase has consequences that are harder to fix retroactively.
The 4:1 ratio is the number to watch. If it narrows -- if safety and oversight work accelerates to match capability work -- the second equilibrium becomes achievable. If it stays at 4:1 or widens, the first equilibrium is where we land, and the safety work becomes remediation rather than prevention. The ~4:1 aggregate ratio (averaging ~4:1 but varying from 1.5:1 to 21:1 month-to-month) is the number to watch. If it narrows -- if safety and oversight work accelerates to match capability work -- the second equilibrium becomes achievable. If it stays at ~4:1 or widens, the first equilibrium is where we land, and the safety work becomes remediation rather than prevention.
The drafts are being written. The race is on. The outcome depends on whether coordination catches up to creativity. The drafts are being written. The race is on. The outcome depends on whether coordination catches up to creativity.
--- ---
*Analysis based on 434 IETF Internet-Drafts, 557 authors, 628 cross-org convergent ideas, and 11 identified gaps, current as of March 2026. Written by the Architect agent as input for the blog series and as a standalone reference document.* *Analysis based on 434 IETF Internet-Drafts, 557 authors, 130 cross-org convergent ideas, and 11 identified gaps, current as of March 2026. Written by the Architect agent as input for the blog series and as a standalone reference document.*

View File

@@ -4,6 +4,53 @@
--- ---
### 2026-03-08 CODER — Critical fixes: rating clamp, convergence command, blog number correction
**What**: Three fixes addressing data integrity and reproducibility:
1. **Rating clamp range** — Fixed `_clamp_rating()` in `analyzer.py` from `hi=10` to `hi=5`. The rating scale is 1-5 but the clamp allowed values up to 10. Verified no existing ratings in the DB exceed 5 (0 rows affected).
2. **`ietf ideas convergence` subcommand** — Added a `convergence` subcommand under the `ideas` CLI group in `cli.py`. Uses `difflib.SequenceMatcher` at 0.75 threshold to find cross-org convergent ideas. Mirrors the existing `ietf idea-overlap` command but lives under the `ideas` group for discoverability.
3. **Blog post number correction** — The blog series referenced "628 cross-org convergent ideas" throughout. Running `ietf idea-overlap` on the current 419-idea database produces **130 cross-org convergent ideas** (36% of 361 unique clusters). The 628 came from an earlier pipeline run with 1,780 raw ideas. Updated all 8 affected files (posts 00, 05, 06, 07, 08, state-of-ecosystem, and data packages) to use 130 as the headline number, with historical context noting the earlier run's 628 where appropriate.
**Why**: Rating clamp was silently allowing out-of-range values. The 628 number was irreproducible from the current database, making it a credibility risk for the blog series.
**Result**: Clamp fixed (0 DB rows needed correction). Convergence command works (`ietf ideas convergence`). All blog posts now reference the reproducible number (130) with consistent methodology notes.
**Surprise**: The convergence *rate* (36% vs 43%) is roughly consistent across both extraction runs, suggesting the pattern is robust even if absolute counts depend on extraction parameters. This is actually a stronger claim than the raw number.
---
### 2026-03-08 CODER — Security Hardening (FTS5 injection, rate limiting, route safety)
**What**: Three security fixes applied to the web UI and search layer:
1. **FTS5 query sanitization** — Added `sanitize_fts_query()` static method to `HybridSearch` in `search.py` that strips special FTS5 characters (`"`, `*`, `(`, `)`) and boolean operators (`NEAR`, `OR`, `AND`, `NOT`) before passing queries to SQLite FTS5 MATCH. Also applied the same sanitization in `data.py`'s `global_search()`. The old fallback that wrapped unsanitized words in double quotes (itself an injection vector) was removed.
2. **Rate limiting on Claude endpoints** — Added in-memory sliding-window rate limiter (10 req/min/IP) as a decorator on `/api/ask/synthesize` and `/api/compare` — the two endpoints that call Claude and cost tokens. Uses a simple `dict[ip, list[timestamp]]` approach with no new dependencies.
3. **Route converter fix** — Changed all `<path:name>` to `<string:name>` on draft detail routes (3 occurrences). Draft names never contain slashes, so `path` was unnecessarily permissive.
**Why**: Hardening against query injection, API abuse, and path traversal before public deployment.
**Result**: All three fixes applied. No new dependencies introduced.
---
### 2026-03-08 CODER — Codebase Quality: Context Manager, False Positive Filtering, Blog Fix
**What**: Four quality improvements:
1. Added `__enter__`/`__exit__` to `Database` class (db.py) so it can be used as a context manager, matching the pattern already in `Embedder`.
2. Added `include_false_positives` parameter to `drafts_with_ratings()` (default `False`) and `count_drafts()` (default `True` for backward compat). The web UI's `get_overview_stats()` now excludes the 73 false-positive drafts from displayed counts.
3. Checked all ratings.categories JSON values -- all already normalized to short-form names. No migration needed.
4. Fixed arithmetic in Post 03 key takeaways: "roughly 300 distinct proposals" corrected to "roughly 409" (434 - 25 = 409, consistent with the body text on line 85).
**Why**: False positives were being counted in dashboard stats and included in all rating-based analyses (similarity, timeline, categories). The context manager prevents resource leaks. The blog arithmetic contradicted itself within the same post.
**Result**: `Database` now usable with `with` statement. All web UI visualizations automatically exclude false positives via `drafts_with_ratings()` default. Blog post internally consistent.
---
### 2026-03-08 EDITOR — Licensing, IPR, Data Provenance, and Language Hedging Pass
**What**: Five-part editorial and project hygiene pass across the blog series and repository:
1. Created MIT LICENSE file (copyright 2026 Christian Nennemann) -- project claimed "open source" in Post 7 but had no license.
2. Added IETF IPR/BCP 79 notes to Post 07 (Limitations section) and Post 03 (after fragmentation costs section), reminding implementers to check the IETF IPR disclosure database before building on discussed drafts.
3. Qualified all remaining unqualified "4:1" safety ratio references across posts 00, 04, 06, 07, 08, and state-of-ecosystem.md with "averaging ~4:1 but varying from 1.5:1 to 21:1 month-to-month."
4. Added "Analysis based on IETF Datatracker data collected through March 2026" freeze-date note to all 10 blog posts (00 through 08 plus state-of-ecosystem).
5. Hedged causal language in Post 04: "correlates" -> "appears to correlate", "structural, not attitudinal" -> "appears structural."
**Why**: Pre-publication compliance and accuracy. The 4:1 ratio without qualification overstated precision; the missing LICENSE contradicted the open-source claim; IPR notes are standard practice when discussing IETF drafts; data freeze dates prevent readers from assuming currency.
**Result**: All 10 blog posts updated, LICENSE file created. 20+ individual edits across 10 files.
### 2026-03-08 WRITER/EDITOR — Factual Accuracy Pass Across All Blog Posts ### 2026-03-08 WRITER/EDITOR — Factual Accuracy Pass Across All Blog Posts
**What**: Comprehensive factual accuracy fix across all 10 blog series files (posts 00-08 plus state-of-ecosystem), driven by three review documents (review-statistics.md, review-legal.md, review-science.md). Key changes: **What**: Comprehensive factual accuracy fix across all 10 blog series files (posts 00-08 plus state-of-ecosystem), driven by three review documents (review-statistics.md, review-legal.md, review-science.md). Key changes:

View File

@@ -1,7 +1,7 @@
# Cross-Organization Idea Overlap # Cross-Organization Idea Overlap
*Generated 2026-03-03 19:59 UTC — 1780 ideas from 361 drafts* *Generated 2026-03-08 11:42 UTC — 419 ideas from 434 drafts*
**628** ideas appear across 2+ organizations, out of 1467 unique idea clusters. **130** ideas appear across 2+ organizations, out of 361 unique idea clusters.
## Top Convergence Points ## Top Convergence Points
@@ -9,70 +9,68 @@ Ideas where the most organizations independently converge:
| # | Idea | Orgs | Drafts | Organizations | | # | Idea | Orgs | Drafts | Organizations |
|--:|------|-----:|-------:|---------------| |--:|------|-----:|-------:|---------------|
| 1 | Fully Adaptive Routing Ethernet (FARE) | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 1 | Fully Adaptive Routing Ethernet for AI Scale-Up Networks | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent |
| 2 | Scale-Up Network Adaptation | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 2 | AI Agent Protocol Framework | 7 | 3 | ANP Open Source Community, China Mobile, China Telecom, China Unicom, Cisco, Five9, Huawei |
| 3 | MoE Expert Parallelization | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 3 | Natural Language Protocol for Agent Communication | 7 | 1 | Fordham University, IBM, Marist University, Purdue University, Service Now, University of Illinois at Urbana-Champaign, eBay Inc. |
| 4 | Tensor Parallelization Support | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 4 | LISP-based geospatial intelligence network | 6 | 1 | Ariga.io, Cisco, Nexar Inc., Oterra.ai, Universitat Politecnica de Catalunya, lispers.net |
| 5 | Ultra-Low Latency Routing | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 5 | External trace ID for NETCONF configuration attribution | 5 | 1 | China Telecom, Everything OPS, Huawei, Swisscom, Telefonica |
| 6 | Ultra-High Throughput Fabric | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 6 | Encrypted payload mechanism for SUIT manifests | 5 | 1 | Arm Limited, Linaro, SECOM CO., LTD., Siemens, Vigil Security, LLC |
| 7 | Adaptive Load Balancing | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 7 | Distributed AI Service Deployment Architecture | 5 | 1 | DONG-EUI University, Daejeon University, ETRI, Huawei, National Institute of Information and Communications Technology |
| 8 | Large Language Model Network Optimization | 14 | 1 | Baidu, Biren Technology, Broadcom, Centec, China Mobile, Cloudnine Information Technologies, Enflame Technology, Huawei, Hygon, Metanet Networking Technology, Moore Threads, Resnics Technology, Ruijie Networks, Tencent | | 8 | AI-Driven Network Operations with Digital Twin | 5 | 1 | China Mobile, China Unicom, Daejeon University, Huawei, Telefonica |
| 9 | Distributed AI Inference Architecture | 9 | 4 | China Mobile, DONG-EUI University, Daejeon University, ETRI, Huawei, National Institute of Information and Communications Technology, Pengcheng Laboratory, Tsinghua Shenzhen International Graduate School & Pengcheng Laboratory, Tsinghua University | | 9 | Deterministic Network Scaling Requirements | 5 | 1 | CAICT, China Mobile, InterDigital, Telefonica, ZTE Corporation |
| 10 | ISAC-Enabled CATS Architecture | 9 | 3 | CAICT, China Mobile, China Unicom, Huawei, InterDigital, InterDigital Europe, Telefonica, Universidad Carlos III de Madrid, ZTE Corporation | | 10 | Network Management Agent (NMA) for Autonomous L4 Networks | 5 | 1 | CAICT, China Mobile, Cisco, Huawei, ZTE Corporation |
| 11 | Agent-to-Agent (A2A) Communication Paradigm | 8 | 5 | CAICT, China Mobile, Department of Computer Science & Engineering, Deutsche Telekom, Huawei, Intelligent Information R and D Division Mobility Platform Research Center, Orange, Telefonica | | 11 | Agent Network Protocol | 5 | 1 | ANP Open Source Community, China Mobile, China Telecom, China Unicom, Huawei |
| 12 | Heterogeneous Hardware AI Deployment | 8 | 3 | DONG-EUI University, Daejeon University, ETRI, Huawei, National Institute of Information and Communications Technology, Pengcheng Laboratory, Tsinghua Shenzhen International Graduate School & Pengcheng Laboratory, Tsinghua University | | 12 | AC/TE YANG Models for Edge AI Placement | 4 | 3 | China Telecom, Futurewei, Huawei, Telefonica |
| 13 | Network Digital Twin Support | 8 | 3 | China Mobile, China Unicom, DONG-EUI University, Daejeon University, ETRI, Huawei, National Institute of Information and Communications Technology, Telefonica | | 13 | EAP Authentication Method using EDHOC | 4 | 2 | Ericsson, Inria, University of Murcia, University of Oviedo |
| 14 | Resource-Constrained Device Optimization | 8 | 2 | Beihang University, DONG-EUI University, Daejeon University, ETRI, Huawei, National Institute of Information and Communications Technology, Xidian University, ZTE Corporation | | 14 | Specialized AI Model Generation for Intent-Based Networking | 4 | 1 | CNR-ISTI, Huawei, SIRIUS Technology, University of Cagliari |
| 15 | Multi-Agent Communication Protocol | 7 | 8 | AsiaInfo, BUPT, China Mobile, China Telecom, China Unicom, Huawei, Zhongguancun Laboratory | | 15 | Cross-Domain Authorization Information Sharing for Multi-Agent Systems | 4 | 1 | Amazon, Cisco, Ory Corp, Inc., Skyfire, Inc. |
| 16 | AI Agent Communication Network (ACN) | 7 | 5 | ANP Open Source Community, China Mobile, China Telecom, China Unicom, Cisco, Five9, Huawei | | 16 | Time-Scoped Dynamic UCMP Policy via YANG Models | 4 | 1 | China Telecom, Futurewei, Huawei, Telefonica |
| 17 | Autonomous Network Agent Standardization Requirements | 7 | 2 | China Telecom, DONG-EUI University, Daejeon University, Everything OPS, Huawei, Swisscom, Telefonica | | 17 | Cloud-Initiated Time-Bounded Network Policy Activation | 4 | 1 | China Telecom, Futurewei, Huawei, Telefonica |
| 18 | LISP-H3 Integration | 7 | 2 | Ariga.io, Cisco, ISI, R.C. ATHENA, Nexar Inc., Oterra.ai, Universitat Politecnica de Catalunya, lispers.net | | 18 | Multi-Model YANG Composition for Dynamic Policies | 4 | 1 | China Telecom, Futurewei, Huawei, Telefonica |
| 19 | H3-based EID Addressing | 7 | 2 | Ariga.io, CAW, Cisco, Nexar Inc., Oterra.ai, Universitat Politecnica de Catalunya, lispers.net | | 19 | Agentic AI for Autonomous Network Management | 4 | 1 | DONG-EUI University, Daejeon University, Everything OPS, Huawei |
| 20 | Natural Language Interchange Protocol (NLIP) | 7 | 1 | Fordham University, IBM, Marist University, Purdue University, Service Now, University of Illinois at Urbana-Champaign, eBay Inc. | | 20 | EST over CoAP with OSCORE and EDHOC | 4 | 1 | Ericsson, Inria, Nexus, RISE |
| 21 | AI-Mediated API Translation | 7 | 1 | Fordham University, IBM, Marist University, Purdue University, Service Now, University of Illinois at Urbana-Champaign, eBay Inc. | | 21 | Constrained BRSKI with CBOR Vouchers | 4 | 1 | Amazon, IoTconsultancy.nl, Sandelman Software Works, vanderstok consultancy |
| 22 | Uniform Agent Communication Interface | 7 | 1 | Fordham University, IBM, Marist University, Purdue University, Service Now, University of Illinois at Urbana-Champaign, eBay Inc. | | 22 | RPL enrollment priority control mechanism | 4 | 1 | Cisco, Huawei, Sandelman Software Works, University of Warsaw |
| 23 | Generative AI Communication Layer | 7 | 1 | Fordham University, IBM, Marist University, Purdue University, Service Now, University of Illinois at Urbana-Champaign, eBay Inc. | | 23 | Hybrid ECDHE-MLKEM Key Agreement | 4 | 1 | Amazon, Cloudflare, PQShield, University of Waterloo |
| 24 | Natural Language Agent Registration | 7 | 1 | Fordham University, IBM, Marist University, Purdue University, Service Now, University of Illinois at Urbana-Champaign, eBay Inc. | | 24 | Extended Key Update with Fresh DH Exchange | 4 | 1 | Münster Univ. of Applied Sciences, Nokia, Siemens, Zscaler |
| 25 | Agentic Network Architecture | 6 | 7 | CNIC, CAS, China Mobile, China Unicom, Huawei, ICT, CAS, UnionPay | | 25 | LISP-Based Unified Control Plane for AI Infrastructure Scaling | 4 | 1 | Cisco, DataraAI, Google, MIPS |
| 26 | ELA Protocol | 6 | 6 | Bitwave, Cisco, Ericsson, Five9, Inria, Sandelman Software Works | | 26 | Cryptographically Verifiable Actor Chain | 4 | 1 | Aryaka, JPMorgan Chase & Co, Oracle, Telefonica |
| 27 | AI Gateway | 6 | 4 | AsiaInfo, BUPT, China Telecom, Huawei, UnionPay, Zhongguancun Laboratory | | 27 | High-Assurance Data-Plane Policy Enforcement | 4 | 1 | Aryaka, JPMorgan Chase & Co, Oracle, Telefonica |
| 28 | ISAC-CATS Integration Pattern | 6 | 3 | Bitwave, China Mobile, Cisco, Five9, InterDigital Europe, Universidad Carlos III de Madrid | | 28 | Transitive Attestation Profile for WIMSE | 4 | 1 | Aryaka, JPMorgan Chase & Co, Oracle, Telefonica |
| 29 | Agent Communication across WAN | 6 | 3 | China Mobile, China Unicom, Deutsche Telekom, Huawei, Orange, Telefonica | | 29 | Identity Portability Binding via Proof of Residency | 4 | 1 | Aryaka, JPMorgan Chase & Co, Oracle, Telefonica |
| 30 | DH-KA MAC Key Derivation | 6 | 2 | Bundesdruckerei GmbH, Ericsson, IDsec Solutions, The Agency for Digital Government, University of Murcia, University of Oviedo | | 30 | Agent Name Service (ANS) | 4 | 1 | Amazon, Cisco, DistributedApps.ai, Intuit |
## Most Overlapping Organization Pairs ## Most Overlapping Organization Pairs
| Org A | Org B | Shared Ideas | Examples | | Org A | Org B | Shared Ideas | Examples |
|-------|-------|-------------:|----------| |-------|-------|-------------:|----------|
| China Mobile | Huawei | 47 | Fully Adaptive Routing Ethernet (FARE); Scale-Up Network Adaptation; MoE Expert Parallelization +44 | | China Mobile | Huawei | 8 | Fully Adaptive Routing Ethernet for AI Scale-Up Networks; AI Agent Protocol Framework; AI-Driven Network Operations with Digital Twin +5 |
| China Unicom | Huawei | 38 | ISAC-Enabled CATS Architecture; Network Digital Twin Support; Multi-Agent Communication Protocol +35 | | China Telecom | Huawei | 8 | AI Agent Protocol Framework; External trace ID for NETCONF configuration attribution; Agent Network Protocol +5 |
| China Mobile | ZTE Corporation | 36 | ISAC-Enabled CATS Architecture; Cross-Domain Agent Coordination; Autonomic Network Device Operation +33 | | China Unicom | Huawei | 8 | AI Agent Protocol Framework; AI-Driven Network Operations with Digital Twin; Agent Network Protocol +5 |
| Huawei | Telefonica | 35 | ISAC-Enabled CATS Architecture; Agent-to-Agent (A2A) Communication Paradigm; Network Digital Twin Support +32 | | Huawei | Telefonica | 7 | External trace ID for NETCONF configuration attribution; AI-Driven Network Operations with Digital Twin; AC/TE YANG Models for Edge AI Placement +4 |
| China Unicom | ZTE Corporation | 28 | ISAC-Enabled CATS Architecture; Cross-Domain Agent Coordination; Autonomic Network Device Operation +25 | | Tsinghua University | Zhongguancun Laboratory | 7 | LLM-Assisted Network Management Framework; AI Agent Metadata Format; Capability-based Agent Discovery Mechanism +4 |
| Pengcheng Laboratory | Tsinghua Shenzhen International Graduate School & Pengcheng Laboratory | 27 | Distributed AI Inference Architecture; Heterogeneous Hardware AI Deployment; Intelligence Delivery Network (IDN) +24 | | Cisco | Huawei | 5 | AI Agent Protocol Framework; Network Management Agent (NMA) for Autonomous L4 Networks; RPL enrollment priority control mechanism +2 |
| China Mobile | China Unicom | 27 | ISAC-Enabled CATS Architecture; Network Digital Twin Support; Multi-Agent Communication Protocol +24 | | China Telecom | Telefonica | 5 | External trace ID for NETCONF configuration attribution; AC/TE YANG Models for Edge AI Placement; Time-Scoped Dynamic UCMP Policy via YANG Models +2 |
| Cisco | Huawei | 27 | AI Agent Communication Network (ACN); Agent Registration and Renewal Protocol; Network Management Agent (NMA) +24 | | China Mobile | ZTE Corporation | 5 | Deterministic Network Scaling Requirements; Network Management Agent (NMA) for Autonomous L4 Networks; Multicast for LLM Inference Synchronization +2 |
| China Telecom | Huawei | 26 | Multi-Agent Communication Protocol; AI Agent Communication Network (ACN); Autonomous Network Agent Standardization Requirements +23 | | Futurewei | Huawei | 5 | AC/TE YANG Models for Edge AI Placement; Time-Scoped Dynamic UCMP Policy via YANG Models; Cloud-Initiated Time-Bounded Network Policy Activation +2 |
| Cisco | Google | 26 | LISP-Based Unified Control Plane; EID-to-RLOC Mapping for AI Networks; Publication-Subscription for AI Data Flow +23 | | Pengcheng Laboratory | Tsinghua Shenzhen International Graduate School & Pengcheng Laboratory | 5 | Decentralized LLM Inference Coordination Framework; Intelligence Delivery Network (IDN); In-Network Inference Protocol (INIP) +2 |
| Cisco | Five9 | 25 | AI Agent Communication Network (ACN); ELA Protocol; ISAC-CATS Integration Pattern +22 | | China Telecom | Futurewei | 4 | AC/TE YANG Models for Edge AI Placement; Time-Scoped Dynamic UCMP Policy via YANG Models; Cloud-Initiated Time-Bounded Network Policy Activation +1 |
| InterDigital | InterDigital Europe | 21 | ISAC-Enabled CATS Architecture; ISAC Network Optimization; Low-Latency Sensing-Communication Requirements +18 | | Futurewei | Telefonica | 4 | AC/TE YANG Models for Edge AI Placement; Time-Scoped Dynamic UCMP Policy via YANG Models; Cloud-Initiated Time-Bounded Network Policy Activation +1 |
| Ericsson | Inria | 21 | ELA Protocol; Zero-touch IoT onboarding; COSE-based EAP Authentication +18 | | Ericsson | Inria | 4 | EAP Authentication Method using EDHOC; EST over CoAP with OSCORE and EDHOC; Zero-touch device enrollment via EDHOC extension +1 |
| China Telecom | China Unicom | 20 | Multi-Agent Communication Protocol; AI Agent Communication Network (ACN); I2NSF-5G Integration Architecture +17 | | Aryaka | JPMorgan Chase & Co | 4 | Cryptographically Verifiable Actor Chain; High-Assurance Data-Plane Policy Enforcement; Transitive Attestation Profile for WIMSE +1 |
| Tsinghua University | Zhongguancun Laboratory | 20 | Capability-based Discovery; Unified RESTful Invocation Interface; Intent-Based Agent Selection Extension +17 | | Aryaka | Oracle | 4 | Cryptographically Verifiable Actor Chain; High-Assurance Data-Plane Policy Enforcement; Transitive Attestation Profile for WIMSE +1 |
| InterDigital Europe | Universidad Carlos III de Madrid | 19 | ISAC-Enabled CATS Architecture; ISAC-CATS Integration Pattern; ISAC Network Optimization +16 | | Aryaka | Telefonica | 4 | Cryptographically Verifiable Actor Chain; High-Assurance Data-Plane Policy Enforcement; Transitive Attestation Profile for WIMSE +1 |
| Pengcheng Laboratory | Tsinghua University | 18 | Distributed AI Inference Architecture; Heterogeneous Hardware AI Deployment; Intelligence Delivery Network (IDN) +15 | | JPMorgan Chase & Co | Oracle | 4 | Cryptographically Verifiable Actor Chain; High-Assurance Data-Plane Policy Enforcement; Transitive Attestation Profile for WIMSE +1 |
| Tsinghua Shenzhen International Graduate School & Pengcheng Laboratory | Tsinghua University | 18 | Distributed AI Inference Architecture; Heterogeneous Hardware AI Deployment; Intelligence Delivery Network (IDN) +15 | | JPMorgan Chase & Co | Telefonica | 4 | Cryptographically Verifiable Actor Chain; High-Assurance Data-Plane Policy Enforcement; Transitive Attestation Profile for WIMSE +1 |
| CAICT | China Mobile | 18 | ISAC-Enabled CATS Architecture; Agent-to-Agent (A2A) Communication Paradigm; Autonomic Network Device Operation +15 | | Oracle | Telefonica | 4 | Cryptographically Verifiable Actor Chain; High-Assurance Data-Plane Policy Enforcement; Transitive Attestation Profile for WIMSE +1 |
| CAICT | ZTE Corporation | 17 | ISAC-Enabled CATS Architecture; Autonomic Network Device Operation; Multi-Data Center Big Data Processing +14 | | Bitwave | Five9 | 4 | Human-in-the-Loop Confirmation Protocol; Normalized API for AI Agents Calling Tools (N-ACT); Agent-to-Tool Protocol +1 |
## Cross-Org Ideas by Type ## Cross-Org Ideas by Type
- **mechanism**: 310 - **protocol**: 37
- **architecture**: 142 - **architecture**: 34
- **pattern**: 136 - **mechanism**: 31
- **protocol**: 120 - **extension**: 23
- **requirement**: 93 - **requirement**: 19
- **extension**: 68 - **pattern**: 17
- **framework**: 5 - **framework**: 2
- **profile**: 2
- **registry**: 1

View File

@@ -229,7 +229,7 @@ class Analyzer:
raise SystemExit(1) raise SystemExit(1)
@staticmethod @staticmethod
def _clamp_rating(value, default: int = 3, lo: int = 1, hi: int = 10) -> int: def _clamp_rating(value, default: int = 3, lo: int = 1, hi: int = 5) -> int:
"""Clamp a rating value to [lo, hi] integers.""" """Clamp a rating value to [lo, hi] integers."""
try: try:
return max(lo, min(hi, int(value))) return max(lo, min(hi, int(value)))

View File

@@ -1421,6 +1421,103 @@ def ideas_filter(min_score: int, dry_run: bool):
db.close() db.close()
@ideas.command("convergence")
@click.option("--threshold", "-t", default=0.75, help="SequenceMatcher ratio threshold (0-1)")
@click.option("--limit", "-n", default=50, help="Max results to show")
@click.option("--list-all", is_flag=True, help="List all convergent idea pairs")
def ideas_convergence(threshold: float, limit: int, list_all: bool):
"""Find cross-org convergent ideas using SequenceMatcher fuzzy matching.
Groups ideas by fuzzy title similarity, then filters to ideas where
2+ distinct organizations independently propose similar concepts.
"""
from collections import defaultdict
from difflib import SequenceMatcher
from .orgs import normalize_org
cfg = _get_config()
db = Database(cfg)
try:
all_ideas = db.all_ideas()
if not all_ideas:
console.print("[yellow]No ideas extracted yet. Run `ietf ideas --all` first.[/]")
return
# Build draft -> org mapping
draft_orgs: dict[str, set[str]] = defaultdict(set)
rows = db.conn.execute(
"""SELECT da.draft_name, a.affiliation
FROM draft_authors da
JOIN authors a ON da.person_id = a.person_id
WHERE a.affiliation != ''"""
).fetchall()
for r in rows:
org = normalize_org(r["affiliation"])
if org and org != "Independent":
draft_orgs[r["draft_name"]].add(org)
# Group similar ideas by fuzzy title matching
idea_groups: list[dict] = []
for idea in all_ideas:
title_lower = idea["title"].lower().strip()
matched = False
for group in idea_groups:
ratio = SequenceMatcher(None, title_lower, group["canonical"]).ratio()
if ratio >= threshold:
group["ideas"].append(idea)
group["drafts"].add(idea["draft_name"])
group["orgs"].update(draft_orgs.get(idea["draft_name"], set()))
matched = True
break
if not matched:
idea_groups.append({
"canonical": title_lower,
"title": idea["title"],
"ideas": [idea],
"drafts": {idea["draft_name"]},
"orgs": set(draft_orgs.get(idea["draft_name"], set())),
})
# Filter to cross-org ideas (2+ orgs)
cross_org = [g for g in idea_groups if len(g["orgs"]) >= 2]
cross_org.sort(key=lambda g: (-len(g["orgs"]), -len(g["drafts"])))
console.print(f"\n[bold]Cross-Organization Idea Convergence[/]")
console.print(f"Threshold: {threshold} | {len(all_ideas)} ideas | "
f"{len(idea_groups)} unique clusters | "
f"[bold green]{len(cross_org)}[/] cross-org convergent\n")
if not cross_org:
console.print("[yellow]No cross-org convergent ideas at this threshold.[/]")
return
show_n = len(cross_org) if list_all else min(limit, len(cross_org))
table = Table(title=f"Cross-Org Convergent Ideas (showing {show_n} of {len(cross_org)})")
table.add_column("#", justify="right", width=4)
table.add_column("Idea", style="bold", max_width=40)
table.add_column("Orgs", justify="right", width=5)
table.add_column("Drafts", justify="right", width=6)
table.add_column("Organizations", max_width=50)
for rank, g in enumerate(cross_org[:show_n], 1):
org_list = ", ".join(sorted(g["orgs"])[:5])
if len(g["orgs"]) > 5:
org_list += f" +{len(g['orgs']) - 5}"
table.add_row(
str(rank), g["title"][:40], str(len(g["orgs"])),
str(len(g["drafts"])), org_list,
)
console.print(table)
console.print(f"\n[bold]Summary[/]: {len(cross_org)} cross-org convergent ideas "
f"out of {len(idea_groups)} unique clusters "
f"({100 * len(cross_org) / len(idea_groups):.0f}%)")
finally:
db.close()
# ── dedup-ideas ───────────────────────────────────────────────────────── # ── dedup-ideas ─────────────────────────────────────────────────────────

View File

@@ -286,6 +286,12 @@ class Database:
self._conn.close() self._conn.close()
self._conn = None self._conn = None
def __enter__(self):
return self
def __exit__(self, *exc):
self.close()
# --- Drafts --- # --- Drafts ---
def upsert_draft(self, draft: Draft) -> None: def upsert_draft(self, draft: Draft) -> None:
@@ -343,8 +349,16 @@ class Database:
).fetchall() ).fetchall()
return [self._row_to_draft(r) for r in rows] return [self._row_to_draft(r) for r in rows]
def count_drafts(self) -> int: def count_drafts(self, include_false_positives: bool = True) -> int:
if include_false_positives:
return self.conn.execute("SELECT COUNT(*) FROM drafts").fetchone()[0] return self.conn.execute("SELECT COUNT(*) FROM drafts").fetchone()[0]
return self.conn.execute(
"""SELECT COUNT(*) FROM drafts d
WHERE NOT EXISTS (
SELECT 1 FROM ratings r
WHERE r.draft_name = d.name AND r.false_positive = 1
)"""
).fetchone()[0]
def search_drafts(self, query: str, limit: int = 50) -> list[Draft]: def search_drafts(self, query: str, limit: int = 50) -> list[Draft]:
rows = self.conn.execute( rows = self.conn.execute(
@@ -408,13 +422,17 @@ class Database:
).fetchall() ).fetchall()
return [self._row_to_draft(r) for r in rows] return [self._row_to_draft(r) for r in rows]
def drafts_with_ratings(self, limit: int = 200) -> list[tuple[Draft, Rating]]: def drafts_with_ratings(
self, limit: int = 200, include_false_positives: bool = False,
) -> list[tuple[Draft, Rating]]:
fp_clause = "" if include_false_positives else "WHERE COALESCE(r.false_positive, 0) = 0"
rows = self.conn.execute( rows = self.conn.execute(
"""SELECT d.*, r.novelty, r.maturity, r.overlap, r.momentum, r.relevance, f"""SELECT d.*, r.novelty, r.maturity, r.overlap, r.momentum, r.relevance,
r.summary, r.novelty_note, r.maturity_note, r.overlap_note, r.summary, r.novelty_note, r.maturity_note, r.overlap_note,
r.momentum_note, r.relevance_note, r.categories as r_categories, r.rated_at r.momentum_note, r.relevance_note, r.categories as r_categories, r.rated_at
FROM drafts d FROM drafts d
JOIN ratings r ON d.name = r.draft_name JOIN ratings r ON d.name = r.draft_name
{fp_clause}
ORDER BY (r.novelty * 0.30 + r.relevance * 0.25 + r.maturity * 0.20 ORDER BY (r.novelty * 0.30 + r.relevance * 0.25 + r.maturity * 0.20
+ r.momentum * 0.15 + (6 - r.overlap) * 0.10) DESC + r.momentum * 0.15 + (6 - r.overlap) * 0.10) DESC
LIMIT ?""", LIMIT ?""",

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
import re
from collections import defaultdict from collections import defaultdict
import numpy as np import numpy as np
@@ -94,15 +95,26 @@ class HybridSearch:
return merged[:top_k] return merged[:top_k]
@staticmethod
def sanitize_fts_query(query: str) -> str:
"""Sanitize a query string for safe use in FTS5 MATCH expressions.
Strips special FTS5 characters and operators (NEAR, OR, AND, NOT)
that could cause injection or query syntax errors.
"""
# Strip all non-alphanumeric, non-whitespace characters
cleaned = re.sub(r'[^\w\s]', '', query)
# Remove FTS5 boolean operators when used as standalone words
cleaned = re.sub(r'\b(NEAR|OR|AND|NOT)\b', '', cleaned, flags=re.IGNORECASE)
# Collapse whitespace
cleaned = re.sub(r'\s+', ' ', cleaned).strip()
return cleaned
def _fts_search(self, query: str, limit: int = 20) -> list[dict]: def _fts_search(self, query: str, limit: int = 20) -> list[dict]:
"""Run FTS5 keyword search, return ranked results.""" """Run FTS5 keyword search, return ranked results."""
try: safe_query = self.sanitize_fts_query(query)
drafts = self.db.search_drafts(query, limit=limit) if not safe_query:
except Exception: return []
# FTS5 can fail on certain query syntax; fallback to simpler search
# Try wrapping each word with quotes for literal matching
words = query.split()
safe_query = " OR ".join(f'"{w}"' for w in words if w.strip())
try: try:
drafts = self.db.search_drafts(safe_query, limit=limit) drafts = self.db.search_drafts(safe_query, limit=limit)
except Exception: except Exception:

View File

@@ -15,6 +15,9 @@ sys.path.insert(0, str(_project_root / "src"))
import csv import csv
import io import io
import json import json
import time
import functools
from collections import defaultdict
from flask import Flask, render_template, request, jsonify, abort, g, Response from flask import Flask, render_template, request, jsonify, abort, g, Response
@@ -50,6 +53,7 @@ from webui.data import (
get_comparison_data, get_comparison_data,
get_ask_search, get_ask_search,
get_ask_synthesize, get_ask_synthesize,
get_category_summary,
global_search, global_search,
) )
@@ -70,6 +74,29 @@ _analytics_db = str(_project_root / "data" / "analytics.db")
init_analytics(app, db_path=_analytics_db) init_analytics(app, db_path=_analytics_db)
# --- Rate limiting for Claude-calling endpoints ---
_rate_limit_store: dict[str, list[float]] = defaultdict(list)
_RATE_LIMIT_MAX = 10 # max requests
_RATE_LIMIT_WINDOW = 60 # per 60 seconds
def rate_limit(f):
"""Simple in-memory rate limiter: max 10 requests per minute per IP."""
@functools.wraps(f)
def wrapper(*args, **kwargs):
ip = request.remote_addr or "unknown"
now = time.time()
# Prune timestamps outside the sliding window
timestamps = _rate_limit_store[ip]
_rate_limit_store[ip] = [t for t in timestamps if now - t < _RATE_LIMIT_WINDOW]
if len(_rate_limit_store[ip]) >= _RATE_LIMIT_MAX:
return jsonify({"error": "Rate limit exceeded. Try again later."}), 429
_rate_limit_store[ip].append(now)
return f(*args, **kwargs)
return wrapper
# --- Database lifecycle (per-request to avoid SQLite threading issues) --- # --- Database lifecycle (per-request to avoid SQLite threading issues) ---
@@ -127,10 +154,12 @@ def drafts():
source=source, source=source,
) )
categories = get_category_counts(db()) categories = get_category_counts(db())
cat_summary = get_category_summary(db(), category) if category else None
return render_template( return render_template(
"drafts.html", "drafts.html",
result=result, result=result,
categories=categories, categories=categories,
cat_summary=cat_summary,
search=search, search=search,
current_cat=category, current_cat=category,
current_source=source, current_source=source,
@@ -140,7 +169,7 @@ def drafts():
) )
@app.route("/drafts/<path:name>") @app.route("/drafts/<string:name>")
def draft_detail(name: str): def draft_detail(name: str):
database = db() database = db()
detail = get_draft_detail(database, name) detail = get_draft_detail(database, name)
@@ -321,8 +350,11 @@ def analytics_dashboard():
@app.route("/about") @app.route("/about")
def about(): def about():
from ietf_analyzer.config import Config
cfg = Config.load()
stats = get_overview_stats(db()) stats = get_overview_stats(db())
return render_template("about.html", stats=stats) return render_template("about.html", stats=stats, search_keywords=cfg.search_keywords,
fetch_since=cfg.fetch_since)
@app.route("/impressum") @app.route("/impressum")
@@ -356,6 +388,7 @@ def ask_page():
@app.route("/api/ask/synthesize", methods=["POST"]) @app.route("/api/ask/synthesize", methods=["POST"])
@admin_required @admin_required
@rate_limit
def api_ask_synthesize(): def api_ask_synthesize():
"""Synthesize an answer via Claude (costs tokens, cached permanently). Returns JSON.""" """Synthesize an answer via Claude (costs tokens, cached permanently). Returns JSON."""
data = request.get_json(force=True, silent=True) data = request.get_json(force=True, silent=True)
@@ -392,6 +425,7 @@ def compare_page():
@app.route("/api/compare", methods=["POST"]) @app.route("/api/compare", methods=["POST"])
@admin_required @admin_required
@rate_limit
def api_compare(): def api_compare():
"""Run Claude comparison for drafts. Returns JSON with comparison text.""" """Run Claude comparison for drafts. Returns JSON with comparison text."""
req_data = request.get_json(force=True, silent=True) req_data = request.get_json(force=True, silent=True)
@@ -572,7 +606,7 @@ def api_monitor():
return jsonify(data) return jsonify(data)
@app.route("/api/drafts/<path:name>") @app.route("/api/drafts/<string:name>")
def api_draft_detail(name: str): def api_draft_detail(name: str):
detail = get_draft_detail(db(), name) detail = get_draft_detail(db(), name)
if not detail: if not detail:
@@ -589,7 +623,7 @@ def api_categories():
return jsonify(data) return jsonify(data)
@app.route("/api/drafts/<path:name>/annotate", methods=["POST"]) @app.route("/api/drafts/<string:name>/annotate", methods=["POST"])
@admin_required @admin_required
def api_annotate(name: str): def api_annotate(name: str):
"""Add or update annotation for a draft.""" """Add or update annotation for a draft."""

View File

@@ -8,7 +8,9 @@ from __future__ import annotations
import json import json
import sys import sys
import time
from collections import Counter, defaultdict from collections import Counter, defaultdict
from functools import lru_cache
from pathlib import Path from pathlib import Path
# Add project root to path so we can import ietf_analyzer # Add project root to path so we can import ietf_analyzer
@@ -19,6 +21,33 @@ if str(_project_root) not in sys.path:
from ietf_analyzer.config import Config from ietf_analyzer.config import Config
from ietf_analyzer.db import Database from ietf_analyzer.db import Database
def _extract_month(time_str: str | None) -> str:
"""Normalize a date string to YYYY-MM format."""
if not time_str:
return "unknown"
if len(time_str) >= 7 and time_str[4] == '-':
return time_str[:7] # Already YYYY-MM-DD
if len(time_str) >= 6 and time_str[:4].isdigit():
return time_str[:4] + '-' + time_str[4:6] # YYYYMMDD → YYYY-MM
return time_str[:7]
# Simple TTL cache for expensive computations (t-SNE, clustering, similarity)
_cache: dict[str, tuple[float, object]] = {}
_CACHE_TTL = 300 # 5 minutes
def _cached(key: str, fn, ttl: float = _CACHE_TTL):
"""Return cached result or compute and cache it."""
now = time.monotonic()
if key in _cache:
ts, val = _cache[key]
if now - ts < ttl:
return val
val = fn()
_cache[key] = (now, val)
return val
def get_db() -> Database: def get_db() -> Database:
"""Get a Database instance using default config.""" """Get a Database instance using default config."""
@@ -27,15 +56,22 @@ def get_db() -> Database:
def get_overview_stats(db: Database) -> dict: def get_overview_stats(db: Database) -> dict:
"""Return high-level stats for the dashboard home page.""" """Return high-level stats for the dashboard home page.
total_drafts = db.count_drafts()
rated_pairs = db.drafts_with_ratings(limit=1000) Excludes drafts flagged as false positives from rated counts.
"""
total_drafts = db.count_drafts(include_false_positives=False)
rated_pairs = db.drafts_with_ratings(limit=1000) # already excludes FPs
rated_count = len(rated_pairs) rated_count = len(rated_pairs)
author_count = db.author_count() author_count = db.author_count()
idea_count = db.idea_count() idea_count = db.idea_count()
gaps = db.all_gaps() gaps = db.all_gaps()
input_tok, output_tok = db.total_tokens_used() input_tok, output_tok = db.total_tokens_used()
# Count false positives separately for transparency
total_all = db.count_drafts(include_false_positives=True)
false_positive_count = total_all - total_drafts
return { return {
"total_drafts": total_drafts, "total_drafts": total_drafts,
"rated_count": rated_count, "rated_count": rated_count,
@@ -44,6 +80,7 @@ def get_overview_stats(db: Database) -> dict:
"gap_count": len(gaps), "gap_count": len(gaps),
"input_tokens": input_tok, "input_tokens": input_tok,
"output_tokens": output_tok, "output_tokens": output_tok,
"false_positive_count": false_positive_count,
} }
@@ -57,6 +94,106 @@ def get_category_counts(db: Database) -> dict[str, int]:
return dict(counts.most_common()) return dict(counts.most_common())
def get_category_summary(db: Database, category: str) -> dict | None:
"""Build a data-driven summary for a category. Returns None if category not found."""
pairs = db.drafts_with_ratings(limit=2000)
all_authors = db.top_authors(limit=500)
# Filter to drafts in this category
cat_pairs = [(d, r) for d, r in pairs if category in r.categories]
if not cat_pairs:
return None
# Author lookup: draft_name -> [author names]
author_drafts_map: dict[str, list[str]] = defaultdict(list)
for name, aff, cnt, drafts in all_authors:
for dn in drafts:
author_drafts_map[dn].append(name)
# Dimension averages
n = len(cat_pairs)
avg = lambda vals: round(sum(vals) / len(vals), 1) if vals else 0
novelty_vals = [r.novelty for _, r in cat_pairs]
maturity_vals = [r.maturity for _, r in cat_pairs]
overlap_vals = [r.overlap for _, r in cat_pairs]
momentum_vals = [r.momentum for _, r in cat_pairs]
relevance_vals = [r.relevance for _, r in cat_pairs]
scores = [r.composite_score for _, r in cat_pairs]
# Top drafts
sorted_pairs = sorted(cat_pairs, key=lambda p: p[1].composite_score, reverse=True)
top_3 = [(d.name, d.title, round(r.composite_score, 1)) for d, r in sorted_pairs[:3]]
# Top authors in this category
author_counter: Counter = Counter()
org_counter: Counter = Counter()
author_aff: dict[str, str] = {}
for name, aff, cnt, drafts in all_authors:
author_aff[name] = aff or ""
for d, r in cat_pairs:
for a in author_drafts_map.get(d.name, []):
author_counter[a] += 1
if author_aff.get(a):
org_counter[author_aff[a]] += 1
top_authors = author_counter.most_common(5)
top_orgs = org_counter.most_common(5)
# Strongest and weakest dimensions
dim_avgs = {
"Novelty": avg(novelty_vals),
"Maturity": avg(maturity_vals),
"Overlap": avg(overlap_vals),
"Momentum": avg(momentum_vals),
"Relevance": avg(relevance_vals),
}
strongest = max(dim_avgs, key=dim_avgs.get)
weakest = min(dim_avgs, key=dim_avgs.get)
# Activity trend: how many are recent (last 6 months)?
recent = sum(1 for d, _ in cat_pairs if d.time and d.time >= "2025-09")
total_all = len(pairs)
# Build text summary
lines = []
lines.append(f"**{n} drafts** ({n * 100 // total_all}% of all rated drafts) "
f"with an average composite score of **{avg(scores):.1f}/5.0**.")
# Dimension profile
lines.append(f"Strongest dimension: **{strongest}** ({dim_avgs[strongest]}), "
f"weakest: **{weakest}** ({dim_avgs[weakest]}).")
# Maturity vs novelty insight
if dim_avgs["Maturity"] < 2.5 and dim_avgs["Novelty"] >= 3.0:
lines.append("This category has **high novelty but low maturity** — many early-stage proposals with fresh ideas that haven't been fully developed yet.")
elif dim_avgs["Maturity"] >= 3.0 and dim_avgs["Novelty"] < 2.5:
lines.append("This category is **mature but less novel** — established approaches being refined rather than introducing fundamentally new concepts.")
elif dim_avgs["Maturity"] >= 3.0 and dim_avgs["Novelty"] >= 3.0:
lines.append("This category shows **both high novelty and maturity** — well-developed proposals with genuinely new contributions.")
# Overlap insight
if dim_avgs["Overlap"] >= 3.5:
lines.append(f"High overlap ({dim_avgs['Overlap']}) suggests **significant duplication** — multiple drafts cover similar ground, which may indicate convergence or fragmentation.")
elif dim_avgs["Overlap"] <= 2.0:
lines.append(f"Low overlap ({dim_avgs['Overlap']}) indicates **diverse approaches** — drafts in this category tackle distinct problems with little redundancy.")
# Activity
if recent > 0:
lines.append(f"**{recent} draft{'s' if recent != 1 else ''}** submitted in the last 6 months, "
f"suggesting {'active' if recent >= 3 else 'moderate'} development.")
return {
"text": " ".join(lines),
"count": n,
"avg_score": avg(scores),
"dimensions": dim_avgs,
"top_drafts": top_3,
"top_authors": top_authors,
"top_orgs": top_orgs,
"strongest": strongest,
"weakest": weakest,
}
def get_drafts_page( def get_drafts_page(
db: Database, db: Database,
page: int = 1, page: int = 1,
@@ -235,6 +372,7 @@ def get_rating_distributions(db: Database) -> dict:
"scores": [], "scores": [],
"categories": [], "categories": [],
"names": [], "names": [],
"sources": [],
} }
for draft, rating in pairs: for draft, rating in pairs:
dims["novelty"].append(rating.novelty) dims["novelty"].append(rating.novelty)
@@ -245,6 +383,7 @@ def get_rating_distributions(db: Database) -> dict:
dims["scores"].append(round(rating.composite_score, 2)) dims["scores"].append(round(rating.composite_score, 2))
dims["categories"].append(rating.categories[0] if rating.categories else "Other") dims["categories"].append(rating.categories[0] if rating.categories else "Other")
dims["names"].append(draft.name) dims["names"].append(draft.name)
dims["sources"].append(getattr(draft, "source", "ietf") or "ietf")
return dims return dims
@@ -256,7 +395,7 @@ def get_timeline_data(db: Database) -> dict:
month_cat: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int)) month_cat: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
for d in all_drafts: for d in all_drafts:
month = d.time[:7] if d.time else "unknown" month = _extract_month(d.time)
r = rating_map.get(d.name) r = rating_map.get(d.name)
if r: if r:
cat = r.categories[0] if r.categories else "Other" cat = r.categories[0] if r.categories else "Other"
@@ -423,6 +562,11 @@ def get_coauthor_network(db: Database, min_shared: int = 1) -> dict:
def get_similarity_graph(db: Database, threshold: float = 0.75) -> dict: def get_similarity_graph(db: Database, threshold: float = 0.75) -> dict:
"""Return draft similarity network (cached)."""
return _cached(f"similarity_{threshold}", lambda: _compute_similarity_graph(db, threshold))
def _compute_similarity_graph(db: Database, threshold: float = 0.75) -> dict:
"""Return draft similarity network for force-directed graph. """Return draft similarity network for force-directed graph.
Returns {nodes: [{name, title, category, score}], Returns {nodes: [{name, title, category, score}],
@@ -496,6 +640,11 @@ def get_cross_org_data(db: Database, limit: int = 20) -> list[dict]:
def get_author_network_full(db: Database) -> dict: def get_author_network_full(db: Database) -> dict:
"""Return author network (cached for 5 min)."""
return _cached("author_network", lambda: _compute_author_network_full(db))
def _compute_author_network_full(db: Database) -> dict:
"""Return enriched co-authorship network with avg scores and cluster info. """Return enriched co-authorship network with avg scores and cluster info.
Returns { Returns {
@@ -596,6 +745,11 @@ def get_author_network_full(db: Database) -> dict:
def get_idea_clusters(db: Database) -> dict: def get_idea_clusters(db: Database) -> dict:
"""Cluster ideas (cached for 5 min)."""
return _cached("idea_clusters", lambda: _compute_idea_clusters(db))
def _compute_idea_clusters(db: Database) -> dict:
"""Cluster ideas by embedding similarity, return clusters + t-SNE scatter. """Cluster ideas by embedding similarity, return clusters + t-SNE scatter.
Uses Ward linkage on L2-normalized embeddings (approximates cosine) with Uses Ward linkage on L2-normalized embeddings (approximates cosine) with
@@ -752,6 +906,11 @@ def get_idea_clusters(db: Database) -> dict:
def get_timeline_animation_data(db: Database) -> dict: def get_timeline_animation_data(db: Database) -> dict:
"""Timeline animation (cached for 5 min)."""
return _cached("timeline_animation", lambda: _compute_timeline_animation_data(db))
def _compute_timeline_animation_data(db: Database) -> dict:
"""Compute t-SNE on all drafts, return points with month info + category_monthly. """Compute t-SNE on all drafts, return points with month info + category_monthly.
t-SNE is computed once on ALL drafts so coordinates are stable across t-SNE is computed once on ALL drafts so coordinates are stable across
@@ -791,7 +950,7 @@ def get_timeline_animation_data(db: Database) -> dict:
for i, name in enumerate(names): for i, name in enumerate(names):
r = rating_map[name] r = rating_map[name]
d = draft_map.get(name) d = draft_map.get(name)
month = (d.time[:7] if d and d.time else "unknown") month = _extract_month(d.time if d else None)
cat = r.categories[0] if r.categories else "Other" cat = r.categories[0] if r.categories else "Other"
month_set.add(month) month_set.add(month)
category_monthly[month][cat] += 1 category_monthly[month][cat] += 1
@@ -856,6 +1015,11 @@ def get_monitor_status(db: Database) -> dict:
def get_citation_graph(db: Database, min_refs: int = 2) -> dict: def get_citation_graph(db: Database, min_refs: int = 2) -> dict:
"""Return citation graph (cached for 5 min)."""
return _cached(f"citation_graph_{min_refs}", lambda: _compute_citation_graph(db, min_refs))
def _compute_citation_graph(db: Database, min_refs: int = 2) -> dict:
"""Return citation network data for force-directed graph. """Return citation network data for force-directed graph.
Returns {nodes: [{id, type, title, influence, ...}], Returns {nodes: [{id, type, title, influence, ...}],
@@ -980,7 +1144,12 @@ def global_search(db: Database, query: str) -> dict:
# 1. Drafts via FTS5 # 1. Drafts via FTS5
try: try:
fts_query = " ".join(f'"{w}"' for w in q.split() if w) import re
fts_query = re.sub(r'[^\w\s]', '', q)
fts_query = re.sub(r'\b(NEAR|OR|AND|NOT)\b', '', fts_query, flags=re.IGNORECASE)
fts_query = re.sub(r'\s+', ' ', fts_query).strip()
if not fts_query:
raise ValueError("empty query after sanitization")
rows = db.conn.execute( rows = db.conn.execute(
"""SELECT d.name, d.title, d.abstract, d.time, d."group" """SELECT d.name, d.title, d.abstract, d.time, d."group"
FROM drafts d FROM drafts d
@@ -1067,10 +1236,12 @@ def global_search(db: Database, query: str) -> dict:
def get_landscape_tsne(db: Database) -> list[dict]: def get_landscape_tsne(db: Database) -> list[dict]:
"""Compute t-SNE from embeddings, return [{name, title, x, y, category, score}]. """Compute t-SNE (cached for 5 min)."""
return _cached("landscape_tsne", lambda: _compute_landscape_tsne(db))
Uses cached coordinates if available, otherwise computes fresh.
""" def _compute_landscape_tsne(db: Database) -> list[dict]:
"""Compute t-SNE from embeddings, return [{name, title, x, y, category, score}]."""
import numpy as np import numpy as np
embeddings = db.all_embeddings() embeddings = db.all_embeddings()

View File

@@ -51,6 +51,72 @@
</div> </div>
</div> </div>
<div class="bg-slate-900 rounded-xl border border-slate-800 p-6 mb-6">
<h2 class="text-lg font-semibold text-white mb-3">Data Collection Methodology</h2>
<p class="text-sm text-slate-400 leading-relaxed mb-4">
Drafts are discovered by searching the
<a href="https://datatracker.ietf.org" class="text-blue-400 hover:text-blue-300 transition">IETF Datatracker API</a>
for documents whose abstract contains any of the following keywords.
Only drafts submitted since <span class="text-slate-200 font-medium">{{ fetch_since }}</span> are included.
</p>
<h3 class="text-sm font-semibold text-slate-300 mb-2">Search Keywords</h3>
<div class="flex flex-wrap gap-2 mb-4">
{% for kw in search_keywords %}
<span class="px-2.5 py-1 bg-blue-500/10 text-blue-400 border border-blue-500/20 rounded-md text-xs font-mono">{{ kw }}</span>
{% endfor %}
</div>
<h3 class="text-sm font-semibold text-slate-300 mb-2">Analysis Pipeline</h3>
<div class="text-sm text-slate-400 leading-relaxed space-y-2">
<p><span class="text-slate-200 font-medium">1. Fetch</span> — Query Datatracker API for each keyword, deduplicate by draft name, download full text.</p>
<p><span class="text-slate-200 font-medium">2. Rate</span> — Claude rates each draft on 5 dimensions (novelty, maturity, overlap, momentum, relevance) from 15, with per-dimension explanations.</p>
<p><span class="text-slate-200 font-medium">3. Categorize</span> — Claude assigns one or more topic categories (e.g., "A2A protocols", "Agent identity/auth").</p>
<p><span class="text-slate-200 font-medium">4. Extract Ideas</span> — Claude extracts distinct technical ideas from each draft, with novelty scores.</p>
<p><span class="text-slate-200 font-medium">5. Embed</span> — Ollama generates vector embeddings for similarity analysis and clustering.</p>
<p><span class="text-slate-200 font-medium">6. Author Network</span> — Author and affiliation data fetched from Datatracker to build collaboration graphs.</p>
<p><span class="text-slate-200 font-medium">7. Gap Analysis</span> — Claude identifies areas where no existing draft adequately addresses a need.</p>
</div>
<div class="mt-4 p-3 bg-slate-800/50 rounded-lg border border-slate-700/50">
<p class="text-xs text-slate-500">
<span class="text-amber-400/70 font-semibold">Note on keyword selection:</span>
Keywords determine which drafts are included. Broad terms like "agent" and "autonomous" cast a wide net
(catching some tangentially related drafts), while specific terms like "ai-agent" and "agentic" target
the core AI agent space. The false-positive flag in ratings helps filter out irrelevant matches.
Suggestions for additional keywords are welcome.
</p>
</div>
</div>
<div class="bg-slate-900 rounded-xl border border-slate-800 p-6 mb-6">
<h2 class="text-lg font-semibold text-white mb-3">Scoring Methodology</h2>
<div class="text-sm text-slate-400 leading-relaxed space-y-3">
<p>Each draft is rated by Claude AI on five dimensions, scored from 1 (lowest) to 5 (highest):</p>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-slate-700 text-left text-xs text-slate-500">
<th class="py-2 pr-4 font-medium">Dimension</th>
<th class="py-2 font-medium">What it measures</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-800/50">
<tr><td class="py-2 pr-4 text-slate-300 font-medium">Novelty</td><td class="py-2">Originality of contribution. Does it introduce genuinely new ideas?</td></tr>
<tr><td class="py-2 pr-4 text-slate-300 font-medium">Maturity</td><td class="py-2">Completeness of the specification. Ready for implementation?</td></tr>
<tr><td class="py-2 pr-4 text-slate-300 font-medium">Overlap</td><td class="py-2">Duplication with other drafts. High = redundant. <em>Inverted in composite score.</em></td></tr>
<tr><td class="py-2 pr-4 text-slate-300 font-medium">Momentum</td><td class="py-2">Activity level. Revisions, WG adoption, multi-org authorship.</td></tr>
<tr><td class="py-2 pr-4 text-slate-300 font-medium">Relevance</td><td class="py-2">How directly related to AI agent infrastructure.</td></tr>
</tbody>
</table>
</div>
<p class="mt-2">
<span class="text-slate-200 font-medium">Composite score</span> = (novelty + maturity + (5 - overlap) + momentum + relevance) / 5.
Overlap is inverted so lower overlap contributes positively.
</p>
</div>
</div>
<div class="bg-slate-900 rounded-xl border border-slate-800 p-6"> <div class="bg-slate-900 rounded-xl border border-slate-800 p-6">
<h2 class="text-lg font-semibold text-white mb-3">Tech Stack</h2> <h2 class="text-lg font-semibold text-white mb-3">Tech Stack</h2>
<ul class="text-sm text-slate-400 space-y-2"> <ul class="text-sm text-slate-400 space-y-2">

View File

@@ -0,0 +1,205 @@
{% extends "base.html" %}
{% set active_page = "analytics" %}
{% block title %}Analytics — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %}
<div class="mb-8">
<h1 class="text-2xl font-bold text-white">Site Analytics</h1>
<p class="text-slate-400 text-sm mt-1">GDPR-compliant traffic overview. No cookies, no personal data stored. Visitor uniqueness estimated via daily-rotating salted hashes (cannot be correlated across days).</p>
</div>
<!-- Stat cards -->
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4 mb-8">
<div class="rounded-xl border border-slate-800 p-4 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-blue-400"></div>
<div class="text-2xl font-bold text-blue-400">{{ data.stats.total_views }}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">Total Views</div>
</div>
<div class="rounded-xl border border-slate-800 p-4 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-emerald-400"></div>
<div class="text-2xl font-bold text-emerald-400">{{ data.stats.total_visitors }}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">Total Visits</div>
</div>
<div class="rounded-xl border border-slate-800 p-4 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-purple-500 to-purple-400"></div>
<div class="text-2xl font-bold text-purple-400">{{ data.stats.total_downloads }}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">Downloads</div>
</div>
<div class="rounded-xl border border-slate-800 p-4 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-amber-500 to-amber-400"></div>
<div class="text-2xl font-bold text-amber-400">{{ data.stats.today_views }}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">Today Views</div>
</div>
<div class="rounded-xl border border-slate-800 p-4 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-cyan-500 to-cyan-400"></div>
<div class="text-2xl font-bold text-cyan-400">{{ data.stats.today_visitors }}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">Today Visitors</div>
</div>
<div class="rounded-xl border border-slate-800 p-4 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-rose-500 to-rose-400"></div>
<div class="text-2xl font-bold text-rose-400">{{ data.stats.week_views }}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">7-Day Views</div>
</div>
<div class="rounded-xl border border-slate-800 p-4 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-indigo-500 to-indigo-400"></div>
<div class="text-2xl font-bold text-indigo-400">{{ data.stats.month_views }}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">30-Day Views</div>
</div>
</div>
<!-- Charts row 1: Daily traffic + Hourly pattern -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Daily Traffic (Last 30 Days)</h2>
<div id="dailyChart" style="height: 300px;"></div>
</div>
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Hourly Pattern (Last 7 Days)</h2>
<div id="hourlyChart" style="height: 300px;"></div>
</div>
</div>
<!-- Charts row 2: Top pages + Top referrers -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Top Pages (Last 30 Days)</h2>
{% if data.top_pages %}
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-slate-700 text-slate-400 text-xs uppercase">
<th class="text-left py-2 pr-4">Path</th>
<th class="text-right py-2 px-2">Views</th>
<th class="text-right py-2 pl-2">Visitors</th>
</tr>
</thead>
<tbody>
{% for page in data.top_pages %}
<tr class="border-b border-slate-800/50 hover:bg-slate-800/30">
<td class="py-1.5 pr-4 text-slate-300 font-mono text-xs truncate max-w-xs">{{ page.path }}</td>
<td class="py-1.5 px-2 text-right text-blue-400">{{ page.views }}</td>
<td class="py-1.5 pl-2 text-right text-emerald-400">{{ page.visitors }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-slate-500 text-sm text-center mt-10">No page view data yet</p>
{% endif %}
</div>
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Top Referrers (Last 30 Days)</h2>
{% if data.top_referrers %}
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-slate-700 text-slate-400 text-xs uppercase">
<th class="text-left py-2 pr-4">Source</th>
<th class="text-right py-2">Visits</th>
</tr>
</thead>
<tbody>
{% for ref in data.top_referrers %}
<tr class="border-b border-slate-800/50 hover:bg-slate-800/30">
<td class="py-1.5 pr-4 text-slate-300 text-xs">{{ ref.referrer }}</td>
<td class="py-1.5 text-right text-amber-400">{{ ref.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-slate-500 text-sm text-center mt-10">No referrer data yet</p>
{% endif %}
</div>
</div>
<!-- Downloads over time -->
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Downloads Over Time</h2>
<div id="downloadsChart" style="height: 250px;"></div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
const PLOTLY_LAYOUT = {
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent',
font: { color: '#94a3b8', family: 'Inter, system-ui, sans-serif', size: 12 },
margin: { t: 20, r: 20, b: 40, l: 50 },
xaxis: { gridcolor: '#1e293b', zerolinecolor: '#334155' },
yaxis: { gridcolor: '#1e293b', zerolinecolor: '#334155' },
};
const PLOTLY_CONFIG = { responsive: true, displayModeBar: false };
// --- Daily traffic ---
const daily = {{ data.daily | tojson }};
if (daily.dates && daily.dates.length > 0) {
Plotly.newPlot('dailyChart', [
{
x: daily.dates, y: daily.views,
type: 'scatter', mode: 'lines+markers',
name: 'Views',
line: { color: '#3b82f6', width: 2 },
marker: { size: 4 },
hovertemplate: '%{x}<br>Views: %{y}<extra></extra>',
},
{
x: daily.dates, y: daily.visitors,
type: 'scatter', mode: 'lines+markers',
name: 'Visitors',
line: { color: '#22c55e', width: 2 },
marker: { size: 4 },
hovertemplate: '%{x}<br>Visitors: %{y}<extra></extra>',
},
], {
...PLOTLY_LAYOUT,
legend: { font: { size: 10, color: '#94a3b8' }, orientation: 'h', y: 1.1 },
xaxis: { ...PLOTLY_LAYOUT.xaxis, title: { text: 'Date', font: { size: 11 } } },
yaxis: { ...PLOTLY_LAYOUT.yaxis, title: { text: 'Count', font: { size: 11 } } },
}, PLOTLY_CONFIG);
} else {
document.getElementById('dailyChart').innerHTML = '<p class="text-slate-500 text-sm text-center mt-20">No traffic data yet — check back after some visits</p>';
}
// --- Hourly pattern ---
const hourly = {{ data.hourly | tojson }};
if (hourly.hours) {
const hourLabels = hourly.hours.map(h => h.toString().padStart(2, '0') + ':00');
Plotly.newPlot('hourlyChart', [{
x: hourLabels, y: hourly.views,
type: 'bar',
marker: { color: 'rgba(168, 85, 247, 0.7)', line: { color: '#a855f7', width: 1 } },
hovertemplate: '%{x}<br>Views: %{y}<extra></extra>',
}], {
...PLOTLY_LAYOUT,
xaxis: { ...PLOTLY_LAYOUT.xaxis, title: { text: 'Hour (UTC)', font: { size: 11 } } },
yaxis: { ...PLOTLY_LAYOUT.yaxis, title: { text: 'Views', font: { size: 11 } } },
}, PLOTLY_CONFIG);
} else {
document.getElementById('hourlyChart').innerHTML = '<p class="text-slate-500 text-sm text-center mt-20">No hourly data yet</p>';
}
// --- Downloads ---
const downloads = {{ data.downloads_daily | tojson }};
if (downloads.dates && downloads.dates.length > 0) {
Plotly.newPlot('downloadsChart', [{
x: downloads.dates, y: downloads.counts,
type: 'bar',
marker: { color: 'rgba(245, 158, 11, 0.7)', line: { color: '#f59e0b', width: 1 } },
hovertemplate: '%{x}<br>Downloads: %{y}<extra></extra>',
}], {
...PLOTLY_LAYOUT,
xaxis: { ...PLOTLY_LAYOUT.xaxis, title: { text: 'Date', font: { size: 11 } } },
yaxis: { ...PLOTLY_LAYOUT.yaxis, title: { text: 'Downloads', font: { size: 11 } } },
}, PLOTLY_CONFIG);
} else {
document.getElementById('downloadsChart').innerHTML = '<p class="text-slate-500 text-sm text-center mt-20">No downloads yet</p>';
}
</script>
{% endblock %}

View File

@@ -102,6 +102,7 @@
<div class="text-slate-300 text-sm leading-relaxed whitespace-pre-line">{{ result.answer }}</div> <div class="text-slate-300 text-sm leading-relaxed whitespace-pre-line">{{ result.answer }}</div>
</div> </div>
{% else %} {% else %}
{% if is_admin %}
<!-- Synthesize button (costs tokens, result is cached permanently) --> <!-- Synthesize button (costs tokens, result is cached permanently) -->
<div class="answer-card rounded-xl border border-slate-800 p-5 mb-6"> <div class="answer-card rounded-xl border border-slate-800 p-5 mb-6">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@@ -119,6 +120,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
<!-- Source drafts (always shown — free) --> <!-- Source drafts (always shown — free) -->

View File

@@ -4,6 +4,7 @@
{% block title %}Author Network — IETF Draft Analyzer{% endblock %} {% block title %}Author Network — IETF Draft Analyzer{% endblock %}
{% block extra_head %} {% block extra_head %}
<script src="/static/js/plotly.min.js"></script>
<script src="/static/js/d3.v7.min.js"></script> <script src="/static/js/d3.v7.min.js"></script>
<style> <style>
#networkSvg { #networkSvg {
@@ -38,7 +39,7 @@
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Author Network</h1> <h1 class="text-2xl font-bold text-white">Author Network</h1>
<p class="text-slate-400 text-sm mt-1">Interactive collaboration graph of {{ network.nodes | length }} authors across {{ orgs | length }} organizations</p> <p class="text-slate-400 text-sm mt-1">Interactive collaboration graph of {{ network.nodes | length }} authors across {{ orgs | length }} organizations. Authors are connected when they co-authored 2+ drafts together. Node size reflects number of drafts authored; color represents organization. Clusters are detected via connected-component analysis (BFS) — authors in the same cluster share direct or indirect co-authorship links.</p>
</div> </div>
<!-- Summary stats --> <!-- Summary stats -->

View File

@@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}IETF Draft Analyzer{% endblock %}</title> <title>{% block title %}IETF Draft Analyzer{% endblock %}</title>
<script src="/static/js/tailwind.js"></script> <script src="/static/js/tailwind.js"></script>
<script src="/static/js/plotly.min.js"></script>
<link rel="stylesheet" href="/static/css/fonts.css"> <link rel="stylesheet" href="/static/css/fonts.css">
<script> <script>
tailwind.config = { tailwind.config = {
@@ -118,10 +117,12 @@
<svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2z"/><circle cx="19" cy="19" r="3" stroke="currentColor" stroke-width="2" fill="none"/></svg> <svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2z"/><circle cx="19" cy="19" r="3" stroke="currentColor" stroke-width="2" fill="none"/></svg>
Idea Clusters Idea Clusters
</a> </a>
{% if is_admin %}
<a href="/gaps" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'gaps' }}"> <a href="/gaps" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'gaps' }}">
<svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4.5c-.77-.833-2.694-.833-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg> <svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4.5c-.77-.833-2.694-.833-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
Gap Explorer Gap Explorer
</a> </a>
{% endif %}
<a href="/timeline" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'timeline' }}"> <a href="/timeline" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'timeline' }}">
<svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> <svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Timeline Timeline
@@ -142,10 +143,16 @@
<svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg> <svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
Authors Authors
</a> </a>
{% if is_admin %}
<a href="/monitor" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'monitor' }}"> <a href="/monitor" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'monitor' }}">
<svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.636 18.364a9 9 0 010-12.728m12.728 0a9 9 0 010 12.728m-9.9-2.829a5 5 0 010-7.07m7.072 0a5 5 0 010 7.07M13 12a1 1 0 11-2 0 1 1 0 012 0z"/></svg> <svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.636 18.364a9 9 0 010-12.728m12.728 0a9 9 0 010 12.728m-9.9-2.829a5 5 0 010-7.07m7.072 0a5 5 0 010 7.07M13 12a1 1 0 11-2 0 1 1 0 012 0z"/></svg>
Monitor Monitor
</a> </a>
<a href="/admin/analytics" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'analytics' }}">
<svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
Analytics
</a>
{% endif %}
<div class="border-t border-slate-800 mt-4 pt-4"> <div class="border-t border-slate-800 mt-4 pt-4">
<a href="/about" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'about' }}"> <a href="/about" class="sidebar-link flex items-center gap-3 px-5 py-2.5 text-sm text-slate-300 {{ 'active' if active_page == 'about' }}">
<svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> <svg class="w-4 h-4 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
@@ -159,6 +166,13 @@
<a href="/impressum" class="hover:text-slate-400 transition">Impressum</a> <a href="/impressum" class="hover:text-slate-400 transition">Impressum</a>
<a href="/datenschutz" class="hover:text-slate-400 transition">Datenschutz</a> <a href="/datenschutz" class="hover:text-slate-400 transition">Datenschutz</a>
</div> </div>
{% if is_admin %}
<div class="flex items-center gap-2 mt-1">
<span class="inline-block w-1.5 h-1.5 rounded-full bg-green-500"></span>
<span class="text-green-500">Admin</span>
<a href="/admin/logout" class="text-slate-600 hover:text-red-400 transition ml-auto">Logout</a>
</div>
{% endif %}
</div> </div>
</aside> </aside>

View File

@@ -36,7 +36,7 @@
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Citation Graph</h1> <h1 class="text-2xl font-bold text-white">Citation Graph</h1>
<p class="text-slate-400 text-sm mt-1">Cross-reference network: {{ graph.stats.draft_count }} drafts referencing {{ graph.stats.rfc_count }} RFCs</p> <p class="text-slate-400 text-sm mt-1">Cross-reference network: {{ graph.stats.draft_count }} drafts referencing {{ graph.stats.rfc_count }} RFCs. References are extracted from each draft's text (RFC mentions, draft citations, BCP references). Node size reflects influence — how many other documents cite it. Highly-cited RFCs represent foundational standards that AI/agent drafts build upon.</p>
</div> </div>
<!-- Summary stats --> <!-- Summary stats -->

View File

@@ -101,6 +101,7 @@
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg> <svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
AI Rating Analysis AI Rating Analysis
</h2> </h2>
<p class="text-xs text-slate-500 mb-3">Rated by Claude AI on five dimensions (15 scale). The composite score is a weighted average. Ratings are generated from the draft's abstract and full text.</p>
{% if draft.rating.summary %} {% if draft.rating.summary %}
<p class="text-sm text-slate-400 mb-5 leading-relaxed">{{ draft.rating.summary }}</p> <p class="text-sm text-slate-400 mb-5 leading-relaxed">{{ draft.rating.summary }}</p>
{% endif %} {% endif %}
@@ -140,6 +141,7 @@
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg> <svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>
Extracted Ideas <span class="text-slate-600 font-normal">({{ draft.ideas|length }})</span> Extracted Ideas <span class="text-slate-600 font-normal">({{ draft.ideas|length }})</span>
</h2> </h2>
<p class="text-xs text-slate-500 mb-3">Technical ideas extracted by Claude AI from the draft text. Each idea is classified by type (protocol, mechanism, framework, architecture) and rated for novelty (N:15).</p>
<div class="space-y-3"> <div class="space-y-3">
{% for idea in draft.ideas %} {% for idea in draft.ideas %}
<div class="bg-slate-800/30 rounded-lg p-4 border border-slate-800/50"> <div class="bg-slate-800/30 rounded-lg p-4 border border-slate-800/50">
@@ -174,7 +176,8 @@
</div> </div>
{% endif %} {% endif %}
<!-- Annotation (notes & tags) --> <!-- Annotation (notes & tags) — admin only -->
{% if is_admin %}
<div class="detail-card rounded-xl border border-slate-800 p-6" id="annotationSection"> <div class="detail-card rounded-xl border border-slate-800 p-6" id="annotationSection">
<h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2"> <h2 class="text-sm font-semibold text-slate-300 mb-3 flex items-center gap-2">
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg> <svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
@@ -207,6 +210,7 @@
</button> </button>
<div id="saveStatus" class="text-xs text-center mt-2 text-slate-600"></div> <div id="saveStatus" class="text-xs text-center mt-2 text-slate-600"></div>
</div> </div>
{% endif %}
</div> </div>
<!-- Right column: Sidebar --> <!-- Right column: Sidebar -->
@@ -242,6 +246,7 @@
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg> <svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>
Standards Readiness Standards Readiness
</h2> </h2>
<p class="text-xs text-slate-500 mb-2">Estimates how close a draft is to becoming a standard, based on six factors: working group adoption, revision count, reference density, citation count, author track record, and momentum signals. Score 0100.</p>
<!-- Gauge --> <!-- Gauge -->
<div class="relative w-full h-6 bg-slate-800 rounded-full overflow-hidden mb-2"> <div class="relative w-full h-6 bg-slate-800 rounded-full overflow-hidden mb-2">
<div class="h-full rounded-full transition-all duration-700 <div class="h-full rounded-full transition-all duration-700

View File

@@ -53,6 +53,11 @@
color: #4ade80; color: #4ade80;
border: 1px solid rgba(34, 197, 94, 0.3); border: 1px solid rgba(34, 197, 94, 0.3);
} }
.source-generated {
background: rgba(168, 85, 247, 0.15);
color: #c084fc;
border: 1px solid rgba(168, 85, 247, 0.3);
}
.cat-pill { .cat-pill {
display: inline-block; display: inline-block;
padding: 1px 8px; padding: 1px 8px;
@@ -122,7 +127,7 @@
<!-- Header --> <!-- Header -->
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Draft Explorer</h1> <h1 class="text-2xl font-bold text-white">Draft Explorer</h1>
<p class="text-slate-400 text-sm mt-1">Browse, search, and filter {{ result.total }} rated Internet-Drafts on AI and agent topics.</p> <p class="text-slate-400 text-sm mt-1">Browse, search, and filter {{ result.total }} rated Internet-Drafts on AI and agent topics. Each draft is scored 15 across five dimensions: <b>Nov</b>(elty) = originality, <b>Mat</b>(urity) = specification completeness, <b>Rel</b>(evance) = importance to AI agents, <b>Mom</b>(entum) = adoption traction, <b>Ovl</b> (overlap) = uniqueness vs other drafts. <b>Rdy</b> = standards readiness (0100). Search works across draft names, titles, summaries, and author names.</p>
</div> </div>
<!-- Filter Bar --> <!-- Filter Bar -->
@@ -157,6 +162,7 @@
<option value="">All sources</option> <option value="">All sources</option>
<option value="ietf" {% if current_source == 'ietf' %}selected{% endif %}>IETF</option> <option value="ietf" {% if current_source == 'ietf' %}selected{% endif %}>IETF</option>
<option value="w3c" {% if current_source == 'w3c' %}selected{% endif %}>W3C</option> <option value="w3c" {% if current_source == 'w3c' %}selected{% endif %}>W3C</option>
<option value="generated" {% if current_source == 'generated' %}selected{% endif %}>Generated</option>
</select> </select>
</div> </div>
<!-- Sort --> <!-- Sort -->
@@ -224,6 +230,64 @@
</form> </form>
</div> </div>
<!-- Category Summary -->
{% if cat_summary and current_cat %}
<div class="bg-slate-900/80 rounded-xl border border-slate-800 p-5 mb-6">
<div class="flex items-start gap-4">
<div class="flex-1">
<h2 class="text-lg font-semibold text-white mb-2">{{ current_cat }}</h2>
<p class="text-sm text-slate-300 leading-relaxed">{{ cat_summary.text }}</p>
</div>
<div class="flex-shrink-0 text-right">
<div class="text-3xl font-bold text-blue-400">{{ cat_summary.avg_score }}<span class="text-base text-slate-500">/5</span></div>
<div class="text-[10px] text-slate-500 uppercase tracking-wider">Avg Score</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4 pt-4 border-t border-slate-800/60">
<!-- Dimension profile -->
<div>
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Rating Profile</h3>
{% for dim, val in cat_summary.dimensions.items() %}
<div class="flex items-center gap-2 mb-1">
<span class="text-xs text-slate-500 w-20">{{ dim }}</span>
<div class="flex-1 bg-slate-800 rounded-full h-1.5 overflow-hidden">
<div class="h-full rounded-full {% if val >= 3.5 %}bg-emerald-500{% elif val >= 2.5 %}bg-amber-500{% else %}bg-red-500{% endif %}"
style="width: {{ (val / 5 * 100)|round }}%"></div>
</div>
<span class="text-xs font-mono {% if val >= 3.5 %}text-emerald-400{% elif val >= 2.5 %}text-amber-400{% else %}text-red-400{% endif %} w-6 text-right">{{ val }}</span>
</div>
{% endfor %}
</div>
<!-- Top drafts -->
<div>
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Top Drafts</h3>
{% for name, title, score in cat_summary.top_drafts %}
<div class="mb-1.5">
<a href="/drafts/{{ name }}" class="text-xs text-blue-400 hover:text-blue-300 transition">{{ title|truncate(50) }}</a>
<span class="text-xs text-slate-600 ml-1">{{ score }}</span>
</div>
{% endfor %}
</div>
<!-- Top authors + orgs -->
<div>
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-2">Key Contributors</h3>
{% for author, count in cat_summary.top_authors %}
<div class="text-xs text-slate-300 mb-0.5">{{ author }} <span class="text-slate-600">({{ count }})</span></div>
{% endfor %}
{% if cat_summary.top_orgs %}
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mt-3 mb-1">Organizations</h3>
{% for org, count in cat_summary.top_orgs[:3] %}
<div class="text-xs text-slate-300 mb-0.5">{{ org }} <span class="text-slate-600">({{ count }})</span></div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
{% endif %}
<!-- Results count + Compare button --> <!-- Results count + Compare button -->
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<p class="text-sm text-slate-500"> <p class="text-sm text-slate-500">
@@ -234,12 +298,14 @@
{% if min_score > 0 %} with score >= <span class="text-blue-400">{{ min_score }}</span>{% endif %} {% if min_score > 0 %} with score >= <span class="text-blue-400">{{ min_score }}</span>{% endif %}
</p> </p>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
{% if is_admin %}
<span id="compareCount" class="text-xs text-slate-600 hidden"><span id="compareNum">0</span> selected</span> <span id="compareCount" class="text-xs text-slate-600 hidden"><span id="compareNum">0</span> selected</span>
<button onclick="goCompare()" id="compareBtn" <button onclick="goCompare()" id="compareBtn"
class="px-4 py-1.5 bg-slate-800 text-slate-500 rounded-lg text-xs font-medium border border-slate-700 cursor-not-allowed transition-colors hidden" class="px-4 py-1.5 bg-slate-800 text-slate-500 rounded-lg text-xs font-medium border border-slate-700 cursor-not-allowed transition-colors hidden"
disabled> disabled>
Compare Selected Compare Selected
</button> </button>
{% endif %}
{% if result.pages > 1 %} {% if result.pages > 1 %}
<p class="text-xs text-slate-600">Page {{ result.page }} of {{ result.pages }}</p> <p class="text-xs text-slate-600">Page {{ result.page }} of {{ result.pages }}</p>
{% endif %} {% endif %}
@@ -268,9 +334,11 @@
</a> </a>
</th> </th>
{% endmacro %} {% endmacro %}
{% if is_admin %}
<th class="px-2 py-3 w-8"> <th class="px-2 py-3 w-8">
<span class="text-xs text-slate-600" title="Select drafts to compare">Cmp</span> <span class="text-xs text-slate-600" title="Select drafts to compare">Cmp</span>
</th> </th>
{% endif %}
{{ sort_header("score", "Score", "w-20") }} {{ sort_header("score", "Score", "w-20") }}
{{ sort_header("name", "Draft") }} {{ sort_header("name", "Draft") }}
{{ sort_header("date", "Date", "w-24 hidden md:table-cell") }} {{ sort_header("date", "Date", "w-24 hidden md:table-cell") }}
@@ -286,11 +354,13 @@
<tbody class="divide-y divide-slate-800/30"> <tbody class="divide-y divide-slate-800/30">
{% for d in result.drafts %} {% for d in result.drafts %}
<tr class="draft-row"> <tr class="draft-row">
{% if is_admin %}
<!-- Compare checkbox --> <!-- Compare checkbox -->
<td class="px-2 py-3 text-center"> <td class="px-2 py-3 text-center">
<input type="checkbox" class="compare-check rounded border-slate-600 bg-slate-800 text-blue-500 focus:ring-blue-500/30 focus:ring-offset-0 w-3.5 h-3.5 cursor-pointer" <input type="checkbox" class="compare-check rounded border-slate-600 bg-slate-800 text-blue-500 focus:ring-blue-500/30 focus:ring-offset-0 w-3.5 h-3.5 cursor-pointer"
data-name="{{ d.name }}" onchange="updateCompare()"> data-name="{{ d.name }}" onchange="updateCompare()">
</td> </td>
{% endif %}
<!-- Score badge --> <!-- Score badge -->
<td class="px-4 py-3"> <td class="px-4 py-3">
<span class="score-badge {% if d.score >= 3.5 %}score-high{% elif d.score >= 2.5 %}score-mid{% else %}score-low{% endif %}"> <span class="score-badge {% if d.score >= 3.5 %}score-high{% elif d.score >= 2.5 %}score-mid{% else %}score-low{% endif %}">
@@ -370,7 +440,7 @@
{% endfor %} {% endfor %}
{% if not result.drafts %} {% if not result.drafts %}
<tr> <tr>
<td colspan="11" class="px-4 py-12 text-center text-slate-500"> <td colspan="{{ 11 if is_admin else 10 }}" class="px-4 py-12 text-center text-slate-500">
<svg class="w-12 h-12 mx-auto mb-3 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-12 h-12 mx-auto mb-3 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg> </svg>

View File

@@ -6,7 +6,7 @@
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Gap Explorer</h1> <h1 class="text-2xl font-bold text-white">Gap Explorer</h1>
<p class="text-slate-400 text-sm mt-1">{{ gaps | length }} identified gaps in AI/agent standards coverage — click any gap to explore details or generate a draft</p> <p class="text-slate-400 text-sm mt-1">{{ gaps | length }} identified gaps in AI/agent standards coverage. Gaps are identified by Claude AI analyzing the full corpus of drafts to find areas where important problems lack adequate proposals. Severity reflects urgency: <span class="text-red-400">critical</span> = blocking issue with no draft addressing it, <span class="text-orange-400">high</span> = partially addressed but incomplete, <span class="text-yellow-400">medium</span> = some coverage exists but more work needed, <span class="text-green-400">low</span> = minor gap or niche concern.</p>
</div> </div>
<!-- Action bar --> <!-- Action bar -->

View File

@@ -3,10 +3,12 @@
{% block title %}Idea Clusters — IETF Draft Analyzer{% endblock %} {% block title %}Idea Clusters — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Idea Clusters</h1> <h1 class="text-2xl font-bold text-white">Idea Clusters</h1>
<p class="text-slate-400 text-sm mt-1">Extracted ideas grouped by semantic similarity — enriched with WG and category data</p> <p class="text-slate-400 text-sm mt-1">Extracted ideas grouped by semantic similarity — enriched with WG and category data. Ideas are embedded using Ollama (nomic-embed-text), then clustered via DBSCAN so that semantically related ideas from different drafts are grouped together. "Cross-WG" clusters indicate ideas that span multiple IETF working groups — potential coordination points.</p>
</div> </div>
<div id="emptyState" class="hidden"> <div id="emptyState" class="hidden">

View File

@@ -3,10 +3,12 @@
{% block title %}Ideas — IETF Draft Analyzer{% endblock %} {% block title %}Ideas — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Extracted Ideas</h1> <h1 class="text-2xl font-bold text-white">Extracted Ideas</h1>
<p class="text-slate-400 text-sm mt-1">{{ data.total }} technical ideas extracted from rated drafts</p> <p class="text-slate-400 text-sm mt-1">{{ data.total }} technical ideas extracted from rated drafts. Claude AI reads each draft and identifies distinct technical contributions — protocols, mechanisms, frameworks, and architectures. Each idea receives a novelty score (N:15) indicating how original it is compared to existing work.</p>
</div> </div>
<!-- Stats header --> <!-- Stats header -->

View File

@@ -3,6 +3,8 @@
{% block title %}Landscape — IETF Draft Analyzer{% endblock %} {% block title %}Landscape — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Draft Landscape</h1> <h1 class="text-2xl font-bold text-white">Draft Landscape</h1>

View File

@@ -3,10 +3,12 @@
{% block title %}Monitor — IETF Draft Analyzer{% endblock %} {% block title %}Monitor — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Live Monitor</h1> <h1 class="text-2xl font-bold text-white">Live Monitor</h1>
<p class="text-slate-400 text-sm mt-1">Track automated monitoring runs and pipeline status</p> <p class="text-slate-400 text-sm mt-1">Track automated monitoring runs and pipeline status. The pipeline fetches new drafts from the IETF Datatracker, rates them with Claude AI, generates embeddings with Ollama, and extracts ideas. Unprocessed counts show drafts waiting at each stage.</p>
</div> </div>
<div id="monitor-app"></div> <div id="monitor-app"></div>

View File

@@ -3,10 +3,20 @@
{% block title %}Overview — IETF Draft Analyzer{% endblock %} {% block title %}Overview — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-8"> <div class="mb-8 flex flex-wrap items-start justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-white">Dashboard Overview</h1> <h1 class="text-2xl font-bold text-white">Dashboard Overview</h1>
<p class="text-slate-400 text-sm mt-1">IETF AI/Agent Internet-Drafts at a glance</p> <p class="text-slate-400 text-sm mt-1">IETF AI/Agent Internet-Drafts at a glance. Drafts are fetched from the IETF Datatracker, then analyzed by Claude AI across five dimensions (novelty, maturity, overlap, momentum, relevance) to produce a composite score from 1.0 to 5.0.</p>
</div>
<a href="/export/obsidian"
class="inline-flex items-center gap-2 px-4 py-2 bg-purple-600/80 hover:bg-purple-500 text-white text-sm font-medium rounded-lg transition-colors flex-shrink-0"
title="Download all research data as an Obsidian vault with interlinked notes, Mermaid charts, and YAML frontmatter">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
Download for Obsidian
</a>
</div> </div>
<!-- Stat cards --> <!-- Stat cards -->
@@ -41,11 +51,13 @@
<!-- Charts row 1: Score distribution + Category donut --> <!-- Charts row 1: Score distribution + Category donut -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5"> <div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Composite Score Distribution</h2> <h2 class="text-sm font-semibold text-slate-300 mb-1">Composite Score Distribution</h2>
<p class="text-xs text-slate-500 mb-3">Weighted average of five AI-rated dimensions (novelty 20%, maturity 20%, uniqueness 20%, momentum 20%, relevance 20%). Higher scores indicate drafts that are novel, mature, unique, gaining traction, and highly relevant to AI agent infrastructure.</p>
<div id="scoreHist" style="height: 300px;"></div> <div id="scoreHist" style="height: 300px;"></div>
</div> </div>
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5"> <div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Drafts by Category</h2> <h2 class="text-sm font-semibold text-slate-300 mb-1">Drafts by Category</h2>
<p class="text-xs text-slate-500 mb-3">Categories are assigned by Claude during analysis. A draft can belong to multiple categories (e.g., both "A2A protocols" and "AI safety/alignment").</p>
<div id="categoryPie" style="height: 300px;"></div> <div id="categoryPie" style="height: 300px;"></div>
</div> </div>
</div> </div>

View File

@@ -3,22 +3,26 @@
{% block title %}Ratings — IETF Draft Analyzer{% endblock %} {% block title %}Ratings — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Rating Analytics</h1> <h1 class="text-2xl font-bold text-white">Rating Analytics</h1>
<p class="text-slate-400 text-sm mt-1">Distribution and analysis of AI-generated ratings</p> <p class="text-slate-400 text-sm mt-1">Distribution and analysis of AI-generated ratings across five dimensions. Each draft is rated 15 on novelty, maturity, overlap, momentum, and relevance by Claude AI, then combined into a weighted composite score.</p>
</div> </div>
<!-- Score Distribution --> <!-- Score Distribution -->
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5 mb-6"> <div class="bg-slate-900 rounded-xl border border-slate-800 p-5 mb-6">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Composite Score Distribution</h2> <h2 class="text-sm font-semibold text-slate-300 mb-1">Composite Score Distribution</h2>
<p class="text-xs text-slate-500 mb-3">The composite score is a weighted average of all five dimensions (each 20%). Scores range from 1.0 (low) to 5.0 (high). Most drafts cluster in the 2.03.5 range.</p>
<div id="scoreHist" style="height: 300px;"></div> <div id="scoreHist" style="height: 300px;"></div>
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<!-- Dimension Box Plots --> <!-- Dimension Box Plots -->
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5"> <div class="bg-slate-900 rounded-xl border border-slate-800 p-5">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Score Distributions by Dimension</h2> <h2 class="text-sm font-semibold text-slate-300 mb-1">Score Distributions by Dimension</h2>
<p class="text-xs text-slate-500 mb-3"><b>Novelty</b>: originality of ideas. <b>Maturity</b>: completeness and specification detail. <b>Overlap</b>: redundancy with other drafts (high = more unique). <b>Momentum</b>: adoption likelihood and community traction. <b>Relevance</b>: importance to AI agent infrastructure.</p>
<div id="dimDist" style="height: 350px;"></div> <div id="dimDist" style="height: 350px;"></div>
</div> </div>
<!-- Category Radar --> <!-- Category Radar -->
@@ -30,7 +34,8 @@
<!-- Scatter: novelty vs maturity --> <!-- Scatter: novelty vs maturity -->
<div class="bg-slate-900 rounded-xl border border-slate-800 p-5 mb-6"> <div class="bg-slate-900 rounded-xl border border-slate-800 p-5 mb-6">
<h2 class="text-sm font-semibold text-slate-300 mb-3">Novelty vs Maturity (bubble = relevance)</h2> <h2 class="text-sm font-semibold text-slate-300 mb-1">Novelty vs Maturity (bubble = relevance)</h2>
<p class="text-xs text-slate-500 mb-3">Each dot is a rated draft. Drafts in the top-right corner are both novel and mature — prime candidates for standardization. Bubble size reflects relevance. Click a point to view the draft.</p>
<div id="scatter" style="height: 450px;"></div> <div id="scatter" style="height: 450px;"></div>
</div> </div>
@@ -38,6 +43,7 @@
<div class="bg-slate-900 rounded-xl border border-slate-800 overflow-hidden"> <div class="bg-slate-900 rounded-xl border border-slate-800 overflow-hidden">
<div class="p-4 border-b border-slate-800"> <div class="p-4 border-b border-slate-800">
<h2 class="text-sm font-semibold text-slate-300">Top 20 Drafts by Composite Score</h2> <h2 class="text-sm font-semibold text-slate-300">Top 20 Drafts by Composite Score</h2>
<p class="text-xs text-slate-500 mt-1">Highest-rated drafts across all dimensions. Green (4+) = strong, amber (3) = moderate, grey (&lt;3) = needs improvement.</p>
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-sm"> <table class="w-full text-sm">
@@ -166,6 +172,7 @@ document.getElementById('scatter').on('plotly_click', function(data) {
momentum: dist.momentum[i], momentum: dist.momentum[i],
overlap: dist.overlap[i], overlap: dist.overlap[i],
category: dist.categories[i], category: dist.categories[i],
source: (dist.sources || [])[i] || 'ietf',
})); }));
drafts.sort((a, b) => b.score - a.score); drafts.sort((a, b) => b.score - a.score);
@@ -185,12 +192,13 @@ document.getElementById('scatter').on('plotly_click', function(data) {
top20.forEach((d, i) => { top20.forEach((d, i) => {
const shortName = d.name.replace('draft-', '').substring(0, 40); const shortName = d.name.replace('draft-', '').substring(0, 40);
const sourceBadge = d.source !== 'ietf' ? ` <span style="display:inline-block;padding:1px 5px;border-radius:4px;font-size:0.55rem;font-weight:600;background:rgba(168,85,247,0.15);color:#c084fc;border:1px solid rgba(168,85,247,0.3);vertical-align:middle">${d.source.toUpperCase()}</span>` : '';
const row = document.createElement('tr'); const row = document.createElement('tr');
row.className = 'hover:bg-slate-800/50 transition'; row.className = 'hover:bg-slate-800/50 transition';
row.innerHTML = ` row.innerHTML = `
<td class="px-4 py-3 text-slate-500 font-mono text-xs">${i + 1}</td> <td class="px-4 py-3 text-slate-500 font-mono text-xs">${i + 1}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
<a href="/drafts/${d.name}" class="text-blue-400 hover:text-blue-300 transition text-xs font-mono">${shortName}</a> <a href="/drafts/${d.name}" class="text-blue-400 hover:text-blue-300 transition text-xs font-mono">${shortName}</a>${sourceBadge}
</td> </td>
<td class="px-4 py-3 text-center"> <td class="px-4 py-3 text-center">
<span class="score-badge ${scoreClass(d.score)}">${d.score.toFixed(2)}</span> <span class="score-badge ${scoreClass(d.score)}">${d.score.toFixed(2)}</span>

View File

@@ -3,10 +3,12 @@
{% block title %}Similarity — IETF Draft Analyzer{% endblock %} {% block title %}Similarity — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Draft Similarity Graph</h1> <h1 class="text-2xl font-bold text-white">Draft Similarity Graph</h1>
<p class="text-slate-400 text-sm mt-1">Force-directed graph of draft-to-draft semantic similarity based on embeddings</p> <p class="text-slate-400 text-sm mt-1">Force-directed graph of draft-to-draft semantic similarity. Each draft is embedded using Ollama (nomic-embed-text) and cosine similarity is computed between all pairs. Edges are drawn between drafts exceeding the similarity threshold — tightly connected clusters indicate drafts covering similar topics. Use the slider to adjust the threshold: higher values show only the strongest relationships.</p>
</div> </div>
<!-- Summary stats --> <!-- Summary stats -->

View File

@@ -3,6 +3,8 @@
{% block title %}Timeline — IETF Draft Analyzer{% endblock %} {% block title %}Timeline — IETF Draft Analyzer{% endblock %}
{% block extra_head %}<script src="/static/js/plotly.min.js"></script>{% endblock %}
{% block content %} {% block content %}
<div class="mb-6"> <div class="mb-6">
<h1 class="text-2xl font-bold text-white">Timeline Animation</h1> <h1 class="text-2xl font-bold text-white">Timeline Animation</h1>
@@ -53,6 +55,22 @@ const points = animData.points;
const months = animData.months; const months = animData.months;
const catMonthly = animData.category_monthly; const catMonthly = animData.category_monthly;
const MONTH_NAMES = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
function fmtMonth(ym) {
if (!ym) return ym;
let y, m;
if (ym.includes('-')) {
[y, m] = ym.split('-');
} else if (ym.length >= 6) {
y = ym.slice(0, 4);
m = ym.slice(4, 6);
} else {
return ym;
}
const mi = parseInt(m, 10) - 1;
return (MONTH_NAMES[mi] || m) + ' ' + y;
}
if (points.length > 0 && months.length > 0) { if (points.length > 0 && months.length > 0) {
// --- Stat cards --- // --- Stat cards ---
@@ -64,7 +82,7 @@ if (points.length > 0 && months.length > 0) {
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-blue-400"></div> <div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-blue-400"></div>
<div class="text-3xl font-bold text-blue-400">${months.length}</div> <div class="text-3xl font-bold text-blue-400">${months.length}</div>
<div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">Months Span</div> <div class="text-xs text-slate-400 mt-1 uppercase tracking-wider">Months Span</div>
<div class="text-xs text-slate-500 mt-0.5">${firstMonth} to ${lastMonth}</div> <div class="text-xs text-slate-500 mt-0.5">${fmtMonth(firstMonth)} ${fmtMonth(lastMonth)}</div>
</div> </div>
<div class="stat-card rounded-xl border border-slate-800 p-5 relative overflow-hidden"> <div class="stat-card rounded-xl border border-slate-800 p-5 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-emerald-400"></div> <div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-emerald-400"></div>
@@ -130,7 +148,7 @@ if (points.length > 0 && months.length > 0) {
// Slider steps // Slider steps
const sliderSteps = months.map(month => ({ const sliderSteps = months.map(month => ({
method: 'animate', method: 'animate',
label: month, label: fmtMonth(month),
args: [[month], { frame: { duration: 500, redraw: true }, transition: { duration: 300 }, mode: 'immediate' }], args: [[month], { frame: { duration: 500, redraw: true }, transition: { duration: 300 }, mode: 'immediate' }],
})); }));
@@ -182,12 +200,12 @@ if (points.length > 0 && months.length > 0) {
// Update badge on animation frame // Update badge on animation frame
const badge = document.querySelector('#monthBadge span'); const badge = document.querySelector('#monthBadge span');
badge.textContent = `Month: ${months[0]} (${firstCount} drafts)`; badge.textContent = `${fmtMonth(months[0])} ${firstCount} drafts`;
document.getElementById('tsneAnim').on('plotly_animatingframe', function(ev) { document.getElementById('tsneAnim').on('plotly_animatingframe', function(ev) {
const month = ev.name; const month = ev.name;
const cumCount = points.filter(p => p.month <= month).length; const cumCount = points.filter(p => p.month <= month).length;
badge.textContent = `Month: ${month} (${cumCount} drafts)`; badge.textContent = `${fmtMonth(month)} ${cumCount} drafts`;
}); });
// Click to navigate // Click to navigate
@@ -211,8 +229,9 @@ if (points.length > 0 && months.length > 0) {
return totalB - totalA; return totalB - totalA;
}); });
const monthLabels = months.map(fmtMonth);
const areaTraces = areaCatList.map((cat, i) => ({ const areaTraces = areaCatList.map((cat, i) => ({
x: months, x: monthLabels,
y: months.map(m => (catMonthly[m] || {})[cat] || 0), y: months.map(m => (catMonthly[m] || {})[cat] || 0),
name: cat, name: cat,
type: 'scatter', type: 'scatter',