How We Built an ELO Rating System for 1v1 Esports Matches

1v1 ELO for Esports

RaiseGG is a competitive gaming platform where players stake USDC on 1v1 matches in CS2, Dota 2, and Deadlock. We needed a fair ranking system.

The Algorithm

Standard ELO with esports modifications:

const K = 32 // Base K-factor
const expectedScore = 1 / (1 + Math.pow(10, (opponentElo - playerElo) / 400))
const newElo = playerElo + K * (actualScore - expectedScore)

Our Modifications

Stake-Weighted K-Factor

Higher stakes = more ELO at risk. A $20 match matters more than a $1 match.

Game-Specific Adjustments

CS2 1v1s are more volatile than Dota 2 1v1s, so K-factors differ by game.

Placement Matches

New players get a higher K-factor for their first 10 matches to quickly reach their true skill level.

City Rankings

Players belong to cities. City ELO is the average of top 10 players from that city. Creates regional rivalries.

Results

After thousands of matches, the system produces stable rankings where:

  • Top players consistently beat lower-ranked opponents
  • Upsets happen but don’t wildly swing rankings
  • New players reach their true rank within ~15 matches

Check the leaderboards: raisegg.gg

Have you implemented ELO or similar rating systems? What edge cases did you hit?

CTF Writeup: PowerAnalysis: Warmup

  1. Challenge OverviewThe challenge provides a remote service that performs encryption. The description hints that the algorithm leaks a “bit” of data during computation. Unlike traditional crypto challenges where you attack the math, here we attack the implementation by observing side-channel leakage.2. The Vulnerability: Side-Channel LeakageThe core of the problem is a Power Analysis vulnerability. In a real-world scenario, a CPU uses slightly more power to process a 1 than a 0, or takes more time if a specific branch of code is executed.In this challenge, we assume the leakage allows us to determine if our guess for a specific bit of the key is correct.3. Exploitation StrategyThe attack is performed bit-by-bit. Instead of brute-forcing $2^{128}$ possibilities (which is impossible), we only need to test each bit position.Connect to the challenge via a Python socket.Iterate through each bit of the key (from 0 to 127).Submit a guess for the current bit.Analyze the leakage: If the response indicates a “hit” (usually through a timing delay or a specific returned value representing power “leaked”), we lock that bit as 1. Otherwise, it’s a 0.Repeat until the full 128-bit key is reconstructed.4. The Solution ScriptHere is the logic used to automate the recovery of the key:Pythonimport socket
    from Crypto.Util.number import long_to_bytes
import socket
import logging
import time
import argparse
from concurrent.futures import ThreadPoolExecutor
from typing import Optional

# Setup professional logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)

class PowerAnalysisClient:
    """A professional interface for interacting with the challenge server."""

    def __init__(self, host: str, port: int, timeout: int = 5):
        self.address = (host, port)
        self.timeout = timeout

    def _communicate(self, payload: str) -> str:
        """Handles the low-level socket lifecycle."""
        try:
            with socket.create_connection(self.address, timeout=self.timeout) as sock:
                sock.sendall(payload.encode() + b'n')
                response = sock.recv(4096).decode().strip()
                return response
        except socket.error as e:
            logger.error(f"Socket error: {e}")
            return ""

    def measure_leak(self, guess: str, samples: int = 1) -> float:
        """
        Measures the side-channel leak. 
        In professional tools, we sample multiple times to filter out network noise.
        """
        timings = []
        for _ in range(samples):
            start = time.perf_counter()
            self._communicate(guess)
            timings.append(time.perf_counter() - start)

        # Using the median or max is often more stable than the mean in network timing
        return max(timings)

class KeyRecoverer:
    """Orchestrates the bit-by-bit recovery of the secret key."""

    def __init__(self, client: PowerAnalysisClient, key_length: int = 128):
        self.client = client
        self.key_length = key_length
        self.recovered_bits = ""

    def recover(self):
        logger.info(f"Starting recovery for {self.key_length}-bit key...")

        for bit_index in range(self.key_length):
            # Test both possibilities for the current bit
            t0 = self.client.measure_leak(self.recovered_bits + "0")
            t1 = self.client.measure_leak(self.recovered_bits + "1")

            if t1 > t0:
                self.recovered_bits += "1"
            else:
                self.recovered_bits += "0"

            if bit_index % 8 == 0:
                logger.info(f"Progress: {bit_index}/{self.key_length} bits recovered.")

        self.finalize()

    def finalize(self):
        key_hex = hex(int(self.recovered_bits, 2))[2:].zfill(self.key_length // 4)
        logger.info(f"SUCCESS: Recovered Key (Hex): {key_hex}")

if __name__ == "__main__":
    # Command-line interface for portability
    parser = argparse.ArgumentParser(description="Professional Power Analysis Tool")
    parser.add_argument("--host", default="saturn.picoctf.net", help="Server hostname")
    parser.add_argument("--port", type=int, required=True, help="Server port")
    args = parser.parse_args()

    client = PowerAnalysisClient(args.host, args.port)
    recoverer = KeyRecoverer(client)
    recoverer.recover()

[Actual socket interaction code goes here]

  1. ConclusionPowerAnalysis: Warmup demonstrates that even mathematically “perfect” encryption like AES can be broken if the physical hardware leaks information. By measuring metadata (power/time) rather than the data itself, we reduced an exponential problem to a linear one.

How I Debug Logs Across Services (and Why I Built a CLI Tool)

CLI demo gif

A while back at work, I was debugging an issue across a few services.
I had the request_id, but the logs were scattered across different files.

Not every environment I work in has full tracing or centralized logging set up, especially local or staging setups, so I often rely on raw logs.

The usual workflow looked something like this:

  • open multiple terminals
  • run grep on each service
  • copy the same ID again and again
  • try to mentally piece together what happened

It worked… but it was slow and honestly pretty frustrating.
I kept thinking — there has to be a simpler way to do this.

Logs are usually:

  • spread across multiple services or files
  • in different formats (plain text, JSON, sometimes pretty logs)
  • hard to follow in order

Even when you do find the right logs, understanding the full request flow takes time. During live debugging, it gets worse — you’re trying to figure things out quickly while jumping between files and commands.

What I Wanted

A simpler workflow that lets me:

  • search by a key (like request_id, trace_id, etc)
  • scan multiple log files at once
  • see everything in a clean, chronological flow

Something lighter than full observability tools, but more structured than chaining grep commands.

Introducing reqlog

So I built a small CLI tool called reqlog.

Basic usage:

reqlog --dir ./logs --key request_id abc123

It searches across log files and prints a timeline like this:

2026-03-20T14:10:00Z [api-gateway]   | request_id=abc123 start request
2026-03-20T14:10:01Z [order-service] | request_id=abc123 fetching order
2026-03-20T14:10:02Z [inventory]     | request_id=abc123 checking stock

Instead of jumping between files, you can just follow the request from start to finish.

Here’s what it looks like in action:
CLI demo gif

Why Not Just Use grep?

grep is great — I still use it all the time.
But when you’re debugging across multiple services, it starts to fall apart:

  • recursive search can touch all files, but you still have to mentally stitch together logs from different services
  • no automatic sense of timeline across services
  • structured logs (like JSON) aren’t convenient
  • lots of copy/paste to track the same request

reqlog sits in between:

more structured than grep, but much simpler than full observability tooling.

Features (v1)

Nothing fancy — just what I needed:

  • works with plain text and JSON logs (single-line)
  • key-based search (--key request_id abc123)
  • scans logs across a directory by default (recursive)
  • optionally filter specific services (--service)
  • time filtering (--since)
  • live tailing (--follow)

Performance

I tested it on multi-million line logs (~9M+ lines across services).

Not as fast as grep (which is highly optimized and only does string matching), but still quick enough for real debugging workflows.
Most searches finish in a couple of seconds.

The extra work (JSON parsing, sorting into a timeline, etc.) adds some overhead — but that’s also what makes the output useful.

Tradeoffs

For v1, I kept things simple:

  • only supports single-line logs
  • no handling for multi-line / pretty logs
  • processes logs sequentially

There’s plenty of room to improve in future versions.

What’s Next

Some things I’m considering:

  • Docker / Kubernetes log support
  • performance improvements (parallel scanning)
  • support for more structured log formats

The main goal is to make debugging across services easier, even without tools like Jaeger or centralized logging systems.

Feedback

If you debug logs across multiple services, I’d love to hear:

  • would something like this be useful for you?
  • what would you want it to do (multi-line logs, k8s/docker support, etc.)?

If this looks interesting, check it out here: reqlog on GitHub

From Learning Capture to Self-Evolving Rules: Adding Verification Sweeps to terraphim-agent

From Learning Capture to Self-Evolving Rules: Adding Verification Sweeps to terraphim-agent

A self-evolving AI coding agent sounds like science fiction. It is not. It is a shell script, a markdown file with grep patterns, and a weekly review discipline.

We have been running terraphim-agent in production for months. It captures every failed bash command from Claude Code and OpenCode, stores them in a persistent learning database, and lets agents query past mistakes before repeating them. The capture loop works. The query system works. The correction mechanism works.

What was missing was verification. We could capture mistakes and add corrections, but we had no way to prove the corrections were being followed. No machine-checkable enforcement. No audit trail. No quantitative measure of whether the system was actually improving.

Then Meta Alchemist published a viral guide on transforming Claude Code into a self-evolving system, and two ideas jumped out: verification patterns on every rule and session scorecards. We already had the foundation. The article showed us what to build on top.

This post covers what we added, what we deliberately did not copy, and why the combination of a Rust CLI with a thin shell verification layer is more robust than an all-in-JSONL approach.

If you have not read the foundation post on configuring terraphim-agent for Claude Code and OpenCode, start there. This post assumes you have the capture system running.

What we already had

Before reading the Meta Alchemist article, our learning infrastructure had three layers:

Layer 1: Learning capture (PostToolUse hook)

Every failed bash command in Claude Code triggers our post_tool_use.sh hook. The hook extracts the command, exit code, and error output, then pipes them to terraphim-agent learn hook --format claude. The learning is stored as a structured file in ~/.local/share/terraphim/learnings/ (global) or .terraphim/learnings/ (project-scoped).

# What the hook does on every failed command:
terraphim-agent learn capture "$COMMAND" --error "$ERROR_OUTPUT" --exit-code "$EXIT_CODE"

The design is fail-open: if terraphim-agent is missing or crashes, the hook passes through silently. An observability tool must never break the tool it observes.

Layer 2: Safety guard (PreToolUse hook)

Before any bash command executes, our pre_tool_use.sh hook runs two checks:

  1. terraphim-agent guard --json blocks destructive commands (rm -rf, git push –force, etc.)
  2. terraphim-agent replace --role "Terraphim Engineer" performs knowledge graph text replacement (npm -> bun, pip -> uv, etc.)

The guard blocks. The replacement corrects. Neither depends on the LLM remembering instructions.

Layer 3: Query and correct

Humans and agents query the learning database:

# List recent learnings
terraphim-agent learn list

# Search by pattern (with synonym expansion via thesaurus)
terraphim-agent learn query "docker"

# Add a correction to a captured learning
terraphim-agent learn correct 3 --correction "Use 'docker compose' (v2 plugin)"

The thesaurus has 20 semantic categories and 160+ synonym mappings. Search for “error” and you find “failure”, “bug”, “issue”. Search for “setup” and you find “configuration”, “install”, “init”. This is not keyword matching. It is structured retrieval.

What was missing

The capture-query-correct loop is a journal. It records mistakes and lets you look them up. What it does not do:

  • Enforce rules mechanically. A rule saying “never use pip” exists only in CLAUDE.md text that the LLM might or might not follow.
  • Verify compliance at session start. No sweep checks whether graduated rules are actually being obeyed.
  • Track improvement quantitatively. No session scorecards. No trend data. No way to prove the system is getting better.
  • Provide an audit trail for rule changes. Rules appear and disappear without record.

These gaps are exactly what the Meta Alchemist article addressed.

What Meta Alchemist proposed

The full guide describes a four-layer system:

  1. Cognitive core (CLAUDE.md): A decision framework Claude runs before writing code, plus completion criteria that must pass before any task is done.
  2. Specialised agents: An architect (plans, read-only) and a reviewer (validates, read-only) that spawn as subagents.
  3. Path-scoped rules: Security rules that only load when editing auth code. API design rules that only activate in handler directories. Keeps context lean.
  4. Evolution engine: A memory system that captures corrections in JSONL files, runs verification sweeps at session start, generates session scorecards, and promotes patterns through a confidence ladder.

The genuinely good ideas:

  • Verify lines on every rule. Each learned rule gets a machine-checkable grep pattern. verify: Grep("...options", path="src/api/") -> 0 matches. The sweep runs the grep and reports PASS/FAIL. This is brilliant. It turns instructions into guardrails.
  • Session scorecards. Quantitative tracking of corrections received, rules checked, rules passed, violations found. Trend detection over time. If corrections are flat or increasing, the rules are not working.
  • Promotion ladder. Corrected once = logged. Corrected twice = auto-promoted to permanent rule. In learned-rules for 10+ sessions = candidate for graduation to CLAUDE.md.
  • Capacity management. Max 50 lines in learned-rules.md forces graduation or pruning. Prevents unbounded growth.

The key quote: “A rule without a verification check is a wish. A rule with a verification check is a guardrail. Only guardrails survive.”

We agree with the principle. We disagree with the implementation.

Where our approaches diverge

The Meta Alchemist article builds everything from scratch using JSONL files parsed by the LLM:

  • corrections.jsonl — user corrections as JSON objects
  • observations.jsonl — verified discoveries
  • violations.jsonl — rule violations caught by sweep
  • sessions.jsonl — session scorecards

This is a reasonable approach if you have no existing infrastructure. We do. terraphim-agent already provides structured file storage with frontmatter, synonym-expanded querying, project/global scoping, and correction chaining. Adding parallel JSONL files would create a split-brain problem: two sources of truth for the same data.

The article also proposes auto-promotion: when the same correction appears twice, it automatically becomes a permanent rule. This is risky. A correction might be context-dependent (correct for one project, wrong for another). It might be a preference rather than a constraint. Auto-promotion without a quality gate means the system accumulates rules without human judgement about which ones deserve to be permanent.

Our approach: capture in terraphim-agent, verify with shell scripts, promote with CTO approval.

The verification layer we added

Three new components, all configuration. No Rust code changes.

learned-rules.md: graduated rules with verify patterns

The file lives at .claude/memory/learned-rules.md. Each rule has three parts: the constraint text, a machine-checkable verify pattern, and a source annotation.

# Learned Rules

Rules graduated from terraphim-agent corrections and CLAUDE.md conventions.
Each rule has a `verify:` pattern checked by the /boot verification sweep.

---

- Never use pip, pip3, or pipx; always use uv instead.
  verify: Grep("pip install|pip3 install|pipx install", path="automation/") -> 0 matches
  [source: CLAUDE.md convention, terraphim-agent learning #4, 2026-03-30]

- Never use npm, yarn, or pnpm; always use bun instead.
  verify: Grep("npm install|yarn add|pnpm add", path="automation/") -> 0 matches
  [source: CLAUDE.md convention, terraphim KG hook replacement, 2026-03-30]

- Never use double dashes in document titles or markdown headings.
  verify: Grep("^#.*--", path="knowledge/") -> 0 matches
  [source: corrected 2x, terraphim-agent learning #5, 2026-03-30]

- Never hardcode API keys as default values in bash scripts.
  verify: Grep("API_KEY=.[a-zA-Z0-9]", path="automation/") -> 0 matches
  [source: MEMORY.md security lesson, 2026-03-30]

The format is deliberately simple. No JSON. No YAML frontmatter. Just markdown that a human can read and a shell script can parse. The verify line follows a consistent pattern:

verify: Grep("regex_pattern", path="scope/") -> N matches

Where -> 0 matches means the pattern should NOT appear (absence check) and -> 1+ matches means the pattern MUST appear (presence check).

Rules without a verify line are flagged as technical debt during evolution review.

verify-sweep.sh: the verification engine

The core script parses learned-rules.md, extracts each verify line, runs the check, and reports PASS/FAIL. It uses rg (ripgrep) when available for native output limiting (no SIGPIPE issues from pipe chains).

#!/bin/bash
# Verification Sweep: parse learned-rules.md, run verify: checks, report PASS/FAIL
# Always exits 0 (advisory tool, never blocks).

set -uo pipefail

RULES_FILE="${1:-.claude/memory/learned-rules.md}"
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
RG="$(which rg 2>/dev/null || echo '')"

TOTAL=0; PASSED=0; FAILED=0; MANUAL=0
current_rule=""

while IFS= read -r line; do
    # Capture rule text (lines starting with "- ")
    if echo "$line" | rg -q '^s*- .+' 2>/dev/null; then
        current_rule=$(echo "$line" | sed 's/^s*- //')
    fi

    # Process verify: lines
    if echo "$line" | rg -q '^s*verify:' 2>/dev/null; then
        TOTAL=$((TOTAL + 1))

        # Skip manual checks
        if echo "$line" | rg -qi 'manual' 2>/dev/null; then
            MANUAL=$((MANUAL + 1))
            echo "SKIP: $current_rule (manual check)"
            continue
        fi

        # Extract pattern, path, and expected count
        pattern=$(echo "$line" | sed -n 's/.*Grep("([^"]*)".*/1/p')
        path_scope=$(echo "$line" | sed -n 's/.*path="([^"]*)".*/1/p')
        expected=$(echo "$line" | sed -n 's/.*-> ([0-9]*).*/1/p')

        [ -z "$pattern" ] && { MANUAL=$((MANUAL + 1)); echo "SKIP: $current_rule (unparseable)"; continue; }

        search_path="${path_scope:-.}"
        [ ! -d "$search_path" ] && [ -d "$PROJECT_ROOT/$search_path" ] && search_path="$PROJECT_ROOT/$search_path"

        # Count matches
        if [ -n "$RG" ]; then
            match_count=$("$RG" -c "$pattern" "$search_path" 2>/dev/null 
                | awk -F: '{s+=$NF} END {print s+0}') || true
        else
            match_count=$(grep -rEc "$pattern" "$search_path" 2>/dev/null 
                | awk -F: '{s+=$NF} END {print s+0}') || true
        fi

        # Compare against expectation
        if [ "$expected" = "0" ]; then
            if [ "$match_count" -eq 0 ]; then
                PASSED=$((PASSED + 1)); echo "PASS: $current_rule"
            else
                FAILED=$((FAILED + 1))
                echo "FAIL: $current_rule (found $match_count matches, expected 0)"
                "$RG" -n --max-count 1 "$pattern" "$search_path" 2>/dev/null 
                    | head -3 | sed 's/^/  >> /' || true
            fi
        else
            if [ "$match_count" -gt 0 ]; then
                PASSED=$((PASSED + 1)); echo "PASS: $current_rule"
            else
                FAILED=$((FAILED + 1))
                echo "FAIL: $current_rule (found 0 matches, expected 1+)"
            fi
        fi
    fi
done < "$RULES_FILE"

echo ""; echo "--- Verification Summary ---"
echo "Rules checked: $TOTAL | Passed: $PASSED | Failed: $FAILED | Skipped: $MANUAL"

Real output from our production environment:

PASS: Never use pip, pip3, or pipx; always use uv instead.
PASS: Never use npm, yarn, or pnpm; always use bun instead.
FAIL: Never use double dashes in document titles or markdown headings. (found 252 matches, expected 0)
  >> knowledge/mem-layer-graph-memory.md:1:# Mem-Layer -- Graph-Based AI Memory System
  >> knowledge/claude-1m-context-ga.md:7:# Claude 1M Context Window -- General Availability
  >> knowledge/ukri-funding-opportunities-2026.md:7:# UKRI Funding Opportunities -- 2026 Active/Recent
FAIL: Use British English spelling in all generated content. (found 316 matches, expected 0)
  >> knowledge/topics/context-engineering.md:41:- locality-of-behavior-dev-community.md
  >> knowledge/topics/conway-vs-strongdm-identity.md:49:- Govern AI agent behavior at runtime
SKIP: Always run date before date-sensitive operations. (manual check)
PASS: Never hardcode API keys as default values in bash scripts.
PASS: Never use git commit --amend in pre-push hooks.

--- Verification Summary ---
Rules checked: 7 | Passed: 4 | Failed: 2 | Skipped: 1

The two failures are from imported external articles that use American English spelling and double dashes. These are expected: imported content is not generated content. This kind of nuance is exactly why we do not auto-fix violations. The sweep surfaces them; the human decides what to do.

/boot skill: session-start verification

The /boot skill wraps the verification sweep into a session-start ritual:

  1. Run date to establish the actual current date (never trust stale context)
  2. Read learned-rules.md to load all graduated rules
  3. Execute verify-sweep.sh to check compliance
  4. Run terraphim-agent learn list to surface recent learnings
  5. Report a one-line summary
Boot complete: 7 rules checked, 4 passed, 2 failed. 10 recent learnings loaded.

The Meta Alchemist article proposes a SessionStart hook to trigger this automatically. Claude Code does not have a SessionStart hook type. Their article assumes one exists. Ours does not. We invoke /boot manually at the start of each session. A manual invocation that runs reliably is better than an automatic hook that does not exist.

The evolution engine

Verification tells you what is wrong. Evolution fixes it over time.

/evolve skill: weekly review with approval gate

The /evolve skill is the mechanism by which the system improves. It runs weekly (or on demand) and does the following:

  1. Gathers corrections from terraphim-agent learn list (recent captures and corrections)
  2. Reads current rules from learned-rules.md
  3. Reads the evolution log from evolution-log.md (to avoid re-proposing rejected rules)
  4. Groups failures by pattern and identifies repeat corrections
  5. Proposes changes using a structured format:
PROPOSE: PROMOTE
Rule: Never use timeout command on macOS (does not exist)
Source: terraphim-agent learning #8, #12
Evidence: Corrected twice across different sessions
Verify: Grep("timeout ", path="automation/") -> 0 matches
Destination: learned-rules.md
Risk: Low. The command genuinely does not exist on macOS.
  1. Waits for CTO approval. No changes are applied until each proposal is individually approved, rejected, or modified.
  2. Logs everything to evolution-log.md: approved changes, rejected proposals, and the reasoning.

This is where we differ most from the Meta Alchemist approach. Their system auto-promotes on the second correction. Ours proposes and waits. The CTO reviews and approves.

Why? Because a correction is context. “Don’t use pip” is correct for our projects. It is not correct for a project that deliberately uses pip. Auto-promotion assumes all corrections are universally true. They are not.

Our principle: A rule without CTO approval is an assumption.

Promotion ladder

Signal Destination
Failed command captured terraphim-agent learn database
Human adds correction Same learning, enriched
Same correction appears twice Flagged for /evolve review
Approved during /evolve learned-rules.md with verify: pattern
Passing for 5+ sessions Candidate for graduation to CLAUDE.md
Rejected during /evolve evolution-log.md (never re-proposed)

The ladder is one-way unless the CTO explicitly overrides. Rejected rules do not come back. Graduated rules do not regress. The evolution log is the audit trail that makes this provable.

Session scorecards

The session-scorecard.sh script generates a quantitative summary:

=== Session Scorecard: 2026-03-30 ===

--- Recent Learnings ---
Total learnings in database: 10
With corrections: 2

--- Verification Sweep ---
Rules checked: 7 | Passed: 4 | Failed: 2 | Skipped: 1

--- Relevant Past Learnings ---
No learnings matching 'cto-executive-system'.

=== End Scorecard ===

Over time, the trend data answers a fundamental question: is the system getting better? If corrections decrease and pass rates increase, the evolution loop is working. If they are flat, the rules are too vague or too disconnected from actual work.

What we deliberately did not build

Engineering is as much about what you leave out as what you put in. Here is what the Meta Alchemist article proposes that we skipped, and why.

JSONL files for corrections and observations

The article creates corrections.jsonl, observations.jsonl, violations.jsonl, and sessions.jsonl. Each is an append-only log of JSON objects that Claude parses at session start.

We already have terraphim-agent learn which provides structured file storage, thesaurus-expanded querying, project/global scoping, and correction chaining. Adding JSONL files would create two sources of truth for the same data. The agent CLI is the single source.

Auto-promotion on second correction

The article promotes automatically when the same correction appears twice. We flag for review. The difference matters:

  • Auto-promotion: fast, no human bottleneck, but accumulates rules without judgement
  • Reviewed promotion: slower, requires CTO time, but every rule is intentional

We chose reviewed promotion because our project spans multiple contexts (CTO executive system, Terraphim AI, client projects). A correction that is right in one context might be wrong in another. The human knows the difference.

SessionStart and Stop hooks

The article configures SessionStart and Stop hooks in Claude Code’s settings.json. These hook types do not exist in Claude Code’s documented hook system. The available types are PreToolUse, PostToolUse, and (in some versions) SubagentStart. The article either assumes a future feature or describes a different version.

We replaced the SessionStart hook with a /boot skill. We replaced the Stop hook with a manual session-scorecard.sh invocation. Both work reliably because they use mechanisms that actually exist.

Path-scoped rules

The article loads different rule files based on which file Claude is editing: security rules for auth code, API design rules for handlers, performance rules everywhere. This is a genuine Claude Code feature (.claude/rules/ with paths: frontmatter).

We skipped it because our project is not a single codebase. The CTO executive system contains knowledge articles, automation scripts, domain models, plans, and publishing workflows. Path-scoped rules make sense for a web application with clear directory boundaries. They are premature for a heterogeneous knowledge system.

Hard capacity cap

The article enforces a maximum of 50 lines in learned-rules.md. If you hit the cap, you must graduate or prune before adding more.

This is a useful forcing function for projects that might accumulate hundreds of rules. We started with 7. When we approach a natural limit, /evolve will recommend pruning. We do not need an artificial constraint to force a behaviour that good engineering practice already demands.

Architecture comparison

Dimension Meta Alchemist terraphim-agent + verify layer
Storage JSONL files parsed by LLM Rust CLI with structured file storage
Capture trigger Custom evolution SKILL.md (auto-triggered) PostToolUse bash hook (fail-open)
Query mechanism LLM reads and interprets JSONL CLI with thesaurus-expanded search
Verification Grep patterns in learned-rules.md Same (we adopted this idea)
Promotion Auto on 2nd correction Manual via /evolve with CTO approval
Audit trail evolution-log.md Same (we adopted this idea)
Session scoring sessions.jsonl (auto-written) session-scorecard.sh (manual)
Cross-tool support Claude Code only Claude Code + OpenCode + any CLI
Safety guard settings.json deny list terraphim-agent guard (pattern matching)
Text replacement Not included terraphim-agent replace (KG-based)

The fundamental difference: Meta Alchemist builds a complete system inside Claude Code’s configuration. We build a thin verification layer on top of an existing CLI. The CLI handles storage, querying, and correction chaining. The verification layer handles enforcement and evolution. Each does what it is good at.

Where this fits in the broader landscape

The idea of self-improving AI coding agents is not new. Several approaches exist:

  • Devin’s knowledge suggestions: captures corrections as project-specific “knowledge” entries that load into future sessions
  • OpenClaw’s metacognitive loops: three-phase review cycle that captures Phase 2 findings as learnings for future Phase 1s
  • Ouroboros pattern: self-modifying agents with constitutional guardrails, event sourcing, and multi-model review chains
  • Compound agency learning architecture: six nested learning loops from failure-to-guardrail up to loop-evolution

Our approach sits between Devin (simple capture) and Ouroboros (full self-modification). We capture automatically, verify mechanically, but promote deliberately. The human stays in the loop for rule changes. The machine handles enforcement.

Getting started

If you already have terraphim-agent configured with the PostToolUse hook (see the foundation post), adding the verification layer takes five steps:

1. Create the directory structure

mkdir -p automation/learning .claude/skills/boot .claude/skills/evolve .claude/memory

2. Seed learned-rules.md

Start with 3 to 5 rules from your existing CLAUDE.md or project conventions. Each rule needs a verify pattern. If you cannot write a grep check for a rule, the rule is too vague.

3. Write verify-sweep.sh

Copy the script from above. Make it executable. Test it:

chmod +x automation/learning/verify-sweep.sh
bash automation/learning/verify-sweep.sh

You should see PASS/FAIL for each rule. If a rule fails, either fix the violation or refine the verify pattern.

4. Create the /boot and /evolve skills

These are SKILL.md files in .claude/skills/boot/ and .claude/skills/evolve/. The boot skill runs the sweep and surfaces learnings. The evolve skill reviews corrections and proposes rule changes. Full skill definitions are in our repository.

5. Add to CLAUDE.md

### Learning Evolution System
- Run /boot at session start to verify learned rules and surface past learnings
- Run /evolve weekly to review corrections and propose rule promotions
- Graduated rules with verify: patterns: .claude/memory/learned-rules.md
- Evolution audit trail: .claude/memory/evolution-log.md

Conclusion

The Meta Alchemist article gave us the idea we were missing: machine-checkable verification patterns on every rule. That single concept transforms a learning journal into an immune system. We credit the article for the insight.

What we brought to the table: a Rust CLI that already handles capture, storage, querying, and correction chaining. The verification layer is 100 lines of bash on top of a structured backend, not 500 lines of JSONL parsing instructions for an LLM.

The combination works. Corrections are captured automatically by the PostToolUse hook. Rules are verified mechanically by the sweep script. Promotions are approved deliberately by a human. The system gets better every week, and we can prove it with session scorecards.

Two principles emerged from building this:

From Meta Alchemist: A rule without a verification check is a wish.

From us: A rule without CTO approval is an assumption.

Only verified, approved guardrails survive.

The terraphim-agent learning system is open source at github.com/terraphim/terraphim-ai. The verification layer described in this post is configuration, not code: shell scripts and markdown files on top of the existing CLI.

This is the third post in a series: Part 1: Configuring terraphim-agent for Claude Code and OpenCode | Part 2: Verification Checklist | Part 3: Self-Evolving Rules (this post)

Java Features

Java is a high-level, object-oriented programming language. This language is very easy to learn and widely used. It is known for its platform independence, reliability, and security. It follows one principle, that is “Write Once, Run Anywhere” principle. It supports various features like portability, robustness, simplicity, multithreading, and high performance, which makes it a popular choice for beginners as well as for developers.

1. Simple Syntax (Java)

Java is designed to be easy to read, write, and understand, especially for beginners.

🔹 Why Java syntax is simple?

  • ✔️ Similar to C/C++ → easy for beginners with basic programming knowledge
  • ✔️ No complicated features like:
    • Pointers ❌
    • Operator overloading ❌
  • ✔️ Clear and structured code style
  • ✔️ Uses English-like keywords (class, public, static, void)
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

🔍 Explanation (Easy):

  • public class Hello → class name
  • main() → starting point of program
  • System.out.println() → print output

2. Object-Oriented (Java)

Java is a pure Object-Oriented Programming (OOP) language (almost everything is based on objects).

🔹 What is Object-Oriented?

👉 Object-Oriented means writing programs using objects and classes instead of just functions.

  • Class → Blueprint (design)
  • Object → Real-world entity created from class

📌 Example:

  • Class = Car 🚗 (design)
  • Object = Your car (real instance)

Java is a pure object-oriented language. It supports core OOP concepts like,

  • Class
  • Objects
  • Inheritance
  • Encapsulation
  • Abstraction
  • Polymorphism

3. Platform Independent (Java)

Java is famous for “Write Once, Run Anywhere (WORA)” ✅

🔹 What does Platform Independent mean?

👉 A Java program written on one system (Windows/Linux/Mac) can run on any other system without changing the code.

🔄 How it works?

Java uses a special process:

1. Write code → .java file
2. Compile → converts to bytecode (.class)
3. Run on JVM → executes on any OS

🔁 Flow:

Java Code → Compiler → Bytecode → JVM → Output

4. Interpreted (Java)

Java is both compiled + interpreted language ✅

🔹 What does “Interpreted” mean?

👉 An interpreted language executes code line by line instead of running fully compiled machine code.

🔄 How Java is Interpreted?

Java works in 2 steps:

1. Compilation

  • java → converted into bytecode (.class) using compiler

2. Interpretation (JVM)

  • JVM reads bytecode line by line
  • Converts it into machine code and runs it

🔁 Flow:

Java Code → Compiler → Bytecode → JVM (Interpreter) → Output

5. Scalable (Java)

👉 Scalable means a program can handle growth (more users, more data, more features) without breaking.

🔹 Simple Real-Life Example

📱 Think about a food delivery app:

  • Initially → 10 users
  • Later → 10,000 users
    👉 If the app is scalable, it will still work smoothly even when users increase.

6. Portable (Java)

👉 Portable means Java programs can run on different systems without changing code or behavior.

🔹 What is Portability?

👉 A program written on one machine can be moved (ported) to another system and still work the same.

🔄 Why Java is Portable?

  • ✔️ Uses standard data types (same size everywhere)
  • ✔️ No system-dependent features (like pointers)
  • ✔️ Bytecode runs on any JVM

7. Secured and Robust (Java)

Java is designed to be both secure 🔐 and robust 💪, making it reliable for real-world applications.

🔐 Security in Java

👉 Java protects systems from harmful code.

🔹 How Java is Secure?

  • ✔️ No pointers → prevents direct memory access
  • ✔️ JVM Security → runs code inside a safe environment (sandbox)
  • ✔️ Bytecode verification → checks code before execution
  • ✔️ Exception handling → prevents system crashes

💪 Robust in Java

👉 Robust means strong and reliable, even when errors occur.

🔹 Why Java is Robust?

– ✔️ Strong memory management
– ✔️ Automatic Garbage Collection
– ✔️ Exception handling system
– ✔️ Type checking (compile + runtime)

8. Memory Management (Java)

👉 Java handles memory automatically, so developers don’t need to manage it manually like in C/C++.

🔹 What is Memory Management?

👉 It is the process of:

  • Allocating memory 🧠
  • Using memory
  • Releasing unused memory

🔄 How Java Manages Memory?

Java uses Heap Memory + Stack Memory + Garbage Collector

9. High Performance (Java)

👉 Java is considered high performance because it runs fast compared to many other high-level languages.

🔹 Why Java is High Performance?

🚀 1. JIT Compiler (Just-In-Time)

👉 Converts bytecode into machine code at runtime

  • Frequently used code → optimized
  • Runs faster after few executions

⚡ 2. Bytecode Optimization

👉 Java compiles code into bytecode, which is:

  • Efficient
  • Optimized for execution

🧵 3. Multithreading Support

👉 Java can run multiple tasks at the same time
👉 Improves overall performance

♻️ 4. Efficient Memory Management

👉 Garbage Collector removes unused objects
👉 Prevents memory issues and improves speed

10. Multithreading (Java)

👉 Multithreading means running multiple tasks at the same time within a single program.

🔹 What is a Thread?

👉 A thread is a small unit of a process
👉 Java allows multiple threads to run concurrently

🔄 Real-Life Example

📱 Mobile phone:

  • Listening to music 🎧
  • Downloading file ⬇️
  • Using WhatsApp 💬

👉 All happening at the same time → Multithreading

11. Rich Standard Library (Java)

👉 Java has a huge collection of built-in classes and packages
👉 So you don’t need to write everything from scratch ✅

🔹 What is Standard Library?

👉 It is a collection of ready-made code provided by Java
👉 You can directly use it in your programs

📦 Example packages:

  • java.lang → basic features
  • java.util → collections (ArrayList, HashMap)
  • java.io → file handling
  • java.net → networking

12. Functional Programming Features (Java) – Simple Explanation

👉 From Java 8, Java supports Functional Programming (FP) concepts
👉 This helps you write short, clean, and modern code ✅

🔹 What is Functional Programming?

👉 Instead of writing long code, you write small functions and pass them around

👉 Focus on:

  • What to do ✔️
  • Not how to do ❌

13. Integration with Other Technologies (Java)

👉 Java can easily connect and work with other technologies like databases, web apps, APIs, and different programming languages.

🔹 What is Integration?

👉 Integration means:
➡️ Java can communicate with other systems
➡️ Share data and work together

14. Support for Mobile and Web Applications (Java)

👉 Java is widely used to build both mobile apps 📱 and web applications 🌐

📱 1. Mobile Applications (Android)

👉 Java is one of the main languages for Android app development

  • Used in Android Studio
  • Builds apps like:
    • WhatsApp 💬
    • Instagram 📸
    • Banking apps 🏦

🌐 2. Web Applications

👉 Java is used to build backend (server-side) of websites

  • Technologies:
    • Servlet
    • JSP
    • Spring Boot

👉 Used in:

  • Login systems 🔐
  • E-commerce sites 🛒
  • Online booking systems 🎟️

15. Documentation and Community Support (Java)

👉 Java has good learning materials and many people to help you ✅

📚 Documentation

👉 Java gives official guides with:

  • Easy explanations
  • Examples
  • Ready reference (API)

👉 You can learn how to use classes like ArrayList, Scanner easily

👨‍💻 Community Support

👉 Many developers use Java, so help is everywhere:

  • Stack Overflow
  • YouTube
  • GitHub
  • Blogs

👉 If you get an error → just search → solution available 😄

Safaricom’s sandbox STK Query API returns FAILED for successful payments. Here’s what’s happening.

Running reconciliation against the Daraja sandbox last week, I got this:

{"checked":3,"matched":0,"skipped":0,"mismatches":[
  {"checkoutRequestId":"ws_CO_26032026133641276708729173",
   "storedStatus":"PENDING","mpesaStatus":"FAILED"},
  {"checkoutRequestId":"ws_CO_26032026111016899708729173",
   "storedStatus":"SUCCESS","mpesaStatus":"FAILED"},
  {"checkoutRequestId":"ws_CO_26032026113146397708729173",
   "storedStatus":"SUCCESS","mpesaStatus":"FAILED"}
]}

The last two entries are the problem. Both have confirmed M-Pesa receipts in the database — UCQ5UAQ403 and UCQ5UAPYRY — with confirmed deductions on the test account. The STK callback delivered ResultCode: 0 for both. Money moved. Safaricom’s own callback said so.

The STK Query API disagrees. It says both payments failed.

I searched Stack Overflow, the Safaricom GitHub repos, every community integration I could find. No prior documentation of this. Not a single issue or comment. It appears to be unreported.

What’s actually happening

Safaricom’s sandbox doesn’t fully simulate the USSD network layer. This is documented behavior — it’s why Pesa Playground exists. The sandbox can’t reliably generate failure states. What’s less documented is the inverse: the sandbox STK Query endpoint apparently cannot reliably confirm success states either. It defaults to FAILED when it can’t definitively resolve a transaction, regardless of what the callback already told you.

The sandbox callback and the sandbox STK Query are not reading from the same source of truth.

How [email protected] handled it

The library refused to act on the contradiction. matched:0 — it checked the payments, found that the STK Query response conflicted with an authoritative stored SUCCESS, and did not overwrite. The PENDING record from the orphaned payment stayed PENDING rather than being incorrectly resolved to FAILED.

That is the correct behavior. A reconciliation system that overwrites SUCCESS with a contradictory query response would be worse than one that does nothing.

What this means for your reconciliation implementation

Two things need to be true in how you handle STK Query responses:

Never overwrite a terminal SUCCESS or confirmed FAILED record based on a query response alone. The callback is the authoritative source. The query is a fallback for records that never received a callback — PENDING only.

Don’t trust sandbox reconciliation results. The sandbox STK Query is not a reliable test surface for this code path. Test your reconciliation logic against a production environment, or accept that sandbox results for this specific path are noise.

The production question

I haven’t run this against a live production environment. Safaricom’s documentation implies the production STK Query returns accurate results — the sandbox is the broken environment, not production. If you’ve tested reconciliation in production and can confirm the query API behaves correctly there, I’d like to know. Leave a comment or find me on the Daraja Discord.

The finding stands regardless: if you’re building reconciliation, your implementation needs to handle contradictory query responses. The sandbox will generate them. Production might too, in edge cases nobody has documented yet.

Tested on 2026-03-26, Daraja sandbox, [email protected]. Full test log in the flutter-daraja-raw repo.

Running State Pattern — LeetCode #1480: Running Sum of 1D Array

Prerequisite: Loop Invariants — the first post in this series covers what an invariant is and how to state one. If the word “invariant” is new to you, start there.

The Problem (15 seconds)

Given an array nums, return an array where each element is the running sum up to that index.

Input:  [1, 2, 3, 4]
Output: [1, 3, 6, 10]

// result[0] = 1
// result[1] = 1 + 2 = 3
// result[2] = 1 + 2 + 3 = 6
// result[3] = 1 + 2 + 3 + 4 = 10

LeetCode #1480 — Running Sum of 1D Array

The Obvious Mistake

Most engineers look at this problem and see a loop inside a loop.

“For each index, add up everything before it.” That’s the instinct. It’s technically correct. It’s also exactly the kind of thinking that produces O(n²) code when O(n) was sitting right there.

// Brute force — recompute the sum from scratch for each index
function runningSum(nums: number[]): number[] {
  const result: number[] = [];
  for (let i = 0; i < nums.length; i++) {
    let sum = 0;
    for (let j = 0; j <= i; j++) {
      sum += nums[j]!;
    }
    result.push(sum);
  }
  return result;
}

O(n²). For each element, summing everything from the start again. But by the time you’re computing result[3], you already know result[2].

You already did that work. Why are you doing it again?

The Moment It Clicks

You’re not computing sums from scratch. You’re carrying one forward.

result[i] = result[i-1] + nums[i]

Each running sum is just the previous running sum plus the current number. You don’t look backward — you carry the answer forward. That shift in mental model — from “compute everything again” to “carry the last answer forward” — is the entire insight. Every problem after this is just a variation on it.

This is the running state pattern: instead of recomputing from scratch, maintain a variable that accumulates as you go.

The Invariant

At step i, sum equals the total of nums[0] through nums[i-1].

After adding nums[i], sum becomes the total through nums[i] — which is exactly result[i].

Let’s trace it:

nums = [1, 2, 3, 4]

Before loop: sum = 0              → total of nothing ✓
i=0: sum = 0 + 1 = 1             → total of [1] ✓        → result = [1]
i=1: sum = 1 + 2 = 3             → total of [1,2] ✓      → result = [1,3]
i=2: sum = 3 + 3 = 6             → total of [1,2,3] ✓    → result = [1,3,6]
i=3: sum = 6 + 4 = 10            → total of [1,2,3,4] ✓  → result = [1,3,6,10]

The invariant held at every step. The result is guaranteed correct.

Solution

TypeScript:

function runningSum(nums: number[]): number[] {
  const result: number[] = [];
  let sum = 0;

  for (const num of nums) {
    sum += num;
    result.push(sum);
  }

  return result;
}

Python:

def running_sum(nums: list[int]) -> list[int]:
    result: list[int] = []
    total = 0

    for num in nums:
        total += num
        result.append(total)

    return result

Complexity: O(n) time, O(n) space (for the result array).

In-place variant (modifies the input):

function runningSum(nums: number[]): number[] {
  for (let i = 1; i < nums.length; i++) {
    nums[i]! += nums[i - 1]!;
  }
  return nums;
}

The Foundation of Everything Hard

Running Sum is the simplest prefix sum — a running total stored at every index. That stored total is the key that unlocks a whole class of harder problems:

  • Subarray Sum Equals K (#560) — does any slice of the array add up to exactly k?
  • Range Sum Query (#303) — what’s the total between index i and index j?
  • Maximum Subarray / Kadane’s (#53) — which contiguous slice has the biggest sum?
  • Product of Array Except Self (#238) — same idea, with multiplication instead of addition

You don’t need to know how to solve these yet. Just notice: every one of them is asking a question that gets dramatically easier once you have the running totals precomputed. The only thing that changes problem to problem is the operation — sum, max, product. The carry-forward structure is identical.

People skip this problem because it’s labeled “Easy.” That’s the mistake. The “Easy” label measures implementation difficulty, not conceptual weight. You’ll hit Subarray Sum Equals K and wonder why prefix sums feel unfamiliar — it’s because you rushed past the problem that introduced them.

It’s Not Just This Problem

Here’s the real pattern underneath all of it.

When you see yourself writing a nested loop where the inner loop starts at index 0 every single time — that’s the signal. You’re recomputing something you already know. The fix is always the same: carry it forward in a variable.

Kadane’s carries the best sum forward. Sliding Window carries the window total forward. Dynamic Programming carries the answer to the previous subproblem forward. Different problems, different names — same move underneath.

The shape changes. The principle doesn’t.

Don’t recalculate. Accumulate. Running sum is the first time you’ll see this idea. It won’t be the last.

How to Recognize This Pattern

You need the accumulator pattern when:

  • The answer at position i depends on all previous positions (not just neighbors)
  • You catch yourself writing a nested loop where the inner loop starts at 0 every time
  • The problem says “running”, “cumulative”, “prefix”, or “so far”

The signal: “I’m recomputing something I already know.” The fix is always the same — carry it forward in a variable.

There’s a version of you that solves this problem in five minutes, checks it off, and moves on without thinking about it again. That version hits a wall at Subarray Sum Equals K and can’t figure out why the prefix sum lookup feels alien.

The other version pauses here. States the invariant. Traces through it manually. Asks: “Where else does this carry-forward idea show up?” That version arrives at Kadane’s with the pattern already loaded. The algorithm makes sense on first reading because the shape is familiar.

This is an easy problem. That doesn’t mean it’s a small one.

What pattern did you miss the first time you solved it — and only understood later when you hit a harder version of the same idea?

Next in the series: Contains Duplicate (#217) — where you’ll learn the “have I seen this before?” pattern with hash sets. Then Valid Anagram (#242) and finally Two Sum (#1) — invariant + lookup + accumulator, all together.

Data Modelling in Power BI: The Foundation Every Analyst Needs.

I started learning Power BI backwards. I jumped straight into building visuals, dragging fields onto a canvas, and then kept wondering why my numbers are wrong. Well, the reason is that I had completely skipped data modelling.

Data modelling-is the invisible architecture behind every Power BI report.
Here is what you need to understand:

What is a Data Model?
A data model is the structure that defines how your tables relate to each other. In Power BI,there is a tool called the Model View, where you connect tables using relationships.

Its often depicted as lines that tell Power BI “this column in this table matches that column in that table.”
The most important concept in data modelling is the difference between two types of tables:

Fact table-They contain measurements i.e. numbers that change over time e.g. sales amounts.
Dimension table-They provide context i.e. the who, what, when and where e.g. Customer names, product categories, customer locations etc.

Star Schema-It is a data modeling technique that organizes data into a central fact table connected to multiple dimension tables, optimizing query performance and simplifying analysis.

*Power BI works best with this structure. Your reports will be faster, your filters will work correctly, and your calculations will give you the right answer. *

Real life Example
Bank loan dashboard
Imagine you work at XYZ bank as a Business Analyst.You are required to provide a report showing loan performance. You have three tables.

1.Loans table.
2.Customers table.
3.Branches Table.

You connect them like this.
Customer ID in the loans table connects to customer ID in the customer’s table.
Branch ID in the loans table connects to branch ID in the branches table.

When a manager clicks on Kakamega branch, Power BI automatically filters the loan table to show only Kakamega branch loans.

So before you drag another visual onto that canvas, check your data model.

LET’S TALK JOINS

Joins-Before your data lands in Power BI, sometimes you need to combine tables first. This is where joins come in. A join is simply a way of saying ”take this table and that table and bring them together based on a matching column”

Think of it like this. You have a class register with student names and IDs. You have a marks sheet with student IDs and scores. A join connects the two so you can see each student’s name alongside their score in one clean table.

There are four main types of joins.

Inner join — give me only the rows that match in both tables. If a student is in the register but not the marks sheet, they are left out completely.

Left join— give me everything from the left table, and match what you can from the right table. If there is no match, leave the right side empty. This is the most common join in data work.

Right join — the opposite of a left join. Keep everything from the right table and match what you can from the left.

Full outer join — give me everything from both tables regardless of whether there is a match. Unmatched rows get blanks on the side that has no data.

SEE BELOW DIAGRAM

What is the most challenging part of data modelling for you?

Road to Golden Jacket: My Journey and Tips for the New AWS SOA-C03 Exam 🚀

When I sat down to take my AWS Certified SysOps Administrator – Associate exam on March 23, I had prepared for a broad range of operational topics. But when the test began, one thing became immediately clear: Amazon CloudWatch was everywhere.

It felt like every other scenario revolved around monitoring, analyzing logs, or troubleshooting performance bottlenecks. Now that AWS has officially transitioned to the new AWS Certified CloudOps Engineer – Associate (SOA-C03) exam, I realize that my heavy CloudWatch experience was actually the best preparation I could have asked for.

Here is a personal look at my exam experience, why CloudWatch is your best friend, and my top tips for anyone tackling the new CloudOps certification.

📈 The CloudWatch Marathon

During my exam on March 23, the monitoring and logging domain was a massive part of the test. I had to know exactly how to trigger alarms, collect logs, and automate responses.

If you are studying for the new SOA-C03 exam, this is even more critical. The domain has been expanded to “Monitoring, Logging, Analysis, Remediation, and Performance Optimization” and now carries the heaviest weight on the exam at 22%,.

To pass, you must understand how to:

  • Configure and manage the CloudWatch agent to collect metrics and logs not just from EC2 instances, but also from Amazon ECS and Amazon EKS clusters,.
  • Set up composite alarms and configure them to invoke AWS services directly or through Amazon EventBridge.
  • Automate remediation tasks using AWS Systems Manager Automation runbooks.

🔄 Bridging the Gap: SysOps to CloudOps

While my exam tested me heavily on traditional monitoring, the new “CloudOps” title reflects a modern shift in cloud operations. If I were taking the SOA-C03 today, here is what I would add to my study list to bridge the gap:

  • The Container Ecosystem: Containers are now officially in-scope. You need to understand Amazon Elastic Container Registry (ECR), Elastic Container Service (ECS), and Elastic Kubernetes Service (EKS),.
  • Infrastructure as Code (IaC): The new exam emphasizes creating and managing stacks using AWS CloudFormation and the AWS Cloud Development Kit (AWS CDK),.
  • Multi-Account Management: Operating in a single account is a thing of the past. You must know how to enforce compliance and manage security across multiple accounts using AWS Organizations, Service Control Policies (SCPs), and AWS Control Tower,.

💡 My Top Tips for Exam Day

Based on my experience in the hot seat, here are my top tips for crushing the exam:

  1. Build a Real Monitoring Lab: Don’t just read the docs. Spin up an EC2 instance, install the CloudWatch agent, and create a custom dashboard,. Set up an alarm to trigger an SNS notification or an EventBridge rule, so you see the automated flow in action.
  2. Master Automation: Understand how to use AWS Systems Manager to automate operational processes and remediate issues,.
  3. Eliminate the Noise: AWS questions are famously wordy and the distractors are designed to look plausible. Find the core requirement like “most cost-effective” or “highly available” and eliminate the answers that don’t fit.
  4. Embrace the AWS Well-Architected Framework: The exam tests your ability to support workloads according to these best practices,. Always think about how to optimize performance and reliability.

Taking this exam validated my practical skills and proved that I could maintain and secure workloads in the cloud. If you are preparing for the SOA-C03, dive deep into CloudWatch and embrace automation!

Have you taken the exam yet? Let me know your experience in the comments below! 👇

How I Built a Live Streaming Platform with PHP, WordPress and WebRTC (Real Case)

JSON to Kotlin extension

Building a real-time streaming platform is usually associated with complex infrastructures and expensive services.

However, in this case, I developed a fully functional live streaming system using PHP, WordPress, and WebRTC — all inside a custom plugin.

This is a real-world implementation, not a theoretical example.

Project Goal

The main idea was to create a platform where:

Users can watch live streams
Private sessions can be started between users
Real-time chat is available
A pay-per-minute system is implemented

All without relying on external streaming services.

⚙️ Tech Stack
Backend: PHP (WordPress plugin)
Database: MySQL ($wpdb)
Streaming: WebRTC (PeerJS)
Frontend: JavaScript + AJAX
Real-Time Streaming with WebRTC

The core of the system is based on WebRTC using PeerJS.

The broadcaster (model) shares their camera:

navigator.mediaDevices.getUserMedia({ video: true, audio: true });

The viewer connects using a dummy stream to avoid permission prompts:

function createDummyStream() {
const canvas = document.createElement(‘canvas’);
return canvas.captureStream();
}

This approach significantly improves UX.

Private Sessions Logic

Private sessions are handled via URL parameters:

/transmision-privada?babe=username

Flow:

User selects a model
A direct peer connection is created
The session starts in real time
Disconnection is detected automatically
💸 Pay-Per-Minute System

One of the most important parts is monetization.

Users are charged per minute
Balance is updated in real time
Broadcasters receive a percentage

Example logic:

function charge_user_per_minute($user_id) {
$balance = get_user_meta($user_id, ‘balance’, true);
return $balance – 3000;
}

This allows building scalable revenue models.

Real-Time Chat (AJAX)

Instead of using WebSockets, I implemented chat using AJAX polling:

Messages sent via admin-ajax.php
Fetched every few seconds
Rendered dynamically

Simple, but effective.

Stream State Management

A custom table tracks active streams:

active_private
active_public
finished

This allows listing live streams and controlling sessions.

Role-Based Access

Two main roles:

Broadcasters (models)
Subscribers (users)

Managed directly through WordPress roles.

Real-World Implementation

This architecture is already being used in production in a live webcam streaming platform with direct payment system, where all these concepts are applied together.

It combines real-time video, chat, and monetization in a single environment.

Final Thoughts

Building a streaming platform with PHP and WordPress is absolutely possible if you combine the right tools.

The real challenge is not just streaming video — it’s integrating:

  • Real-time communication
  • Payment logic
  • User roles
  • System state

This project proves that you can build complex, scalable systems without leaving the WordPress ecosystem.