Rob's Workshop

Inclusive Mayan Sudoku in one evening: building with Kiro at AWS México

Rob Montero ·

TL;DR Play: https://sudoku.robs.ws / Code: github.com/talacha/kiro-sudoku

On Thursday, October 1, AI Tinkerers Mexico City ran El Fantasma en la Máquina, a hands-on workshop with Kiro, Amazon's agentic IDE, at the AWS México offices in Lomas de Chapultepec. Kiro and AWS México sponsored it, seating was capped at 40, and the session ran from 4:00 to 8:00 PM.

By 7:54 PM the repository for my build had its last merge: a Sudoku game in a single 2,408-line HTML file, playable in five languages (including Yucatec Maya with dot-and-bar numerals), plus Terraform that serves it from a private S3 bucket through CloudFront. This piece covers the workshop, what the repository contains, and what I would tighten before calling it finished.

The workshop

This was not a product demo. Manuel Quijano Gaviria, Senior Solutions Architect at AWS México, ran the official AWS Kiro workshop, and every participant built an application in their own environment. Christopher Martinez, chapter leader of AI Tinkerers Mexico City, organized it. Attendees left with Kiro credits to keep building.

What Kiro brings to a build

Kiro's spec workflow turns a request into three files: requirements.md with user stories and acceptance criteria, design.md with the architecture and sequence diagrams, and tasks.md with discrete implementation tasks whose status is tracked as the agent works through them. Independent tasks can run in parallel. Agent hooks are JSON files in .kiro/hooks that fire on events such as a file save, a tool call or the end of a session; the documentation's standard example runs eslint --fix every time the agent saves a TypeScript file.

Kiro Crew is the persistent side of the product: a Gateway that keeps sessions, memory, schedules and task checkpoints across restarts, with a web dashboard plus access from the desktop app, the CLI and chat tools such as Slack. kiro-cli can attach to a running Gateway and continue the same work from a terminal.

The repository carries a trace of that setup in kirocrew/connect.sh, a one-command onboarding script meant to run from the workshop's VS Code server. It installs the SSM Session Manager plugin, reads the outputs of a kirocrew-deployment CloudFormation stack, runs kiro-cli login --use-device-flow on the EC2 host through an SSM session so each person signs in with their own Kiro account, restarts the gateway, and mints a dashboard token. Its final output is one URL that both clears a CloudFront passcode gate and signs the user in. Crew's documentation covers systemd, Docker, SSH and HTTPS tunnels; this script puts the dashboard behind CloudFront instead, so a host can hand sign-in links to other participants.

The game: one file, no dependencies

Markup, styles and logic all live in sudoku.html. There is no build step and no package manifest: open the file, or serve the folder with python3 -m http.server.

Puzzle generation fills the three diagonal 3×3 boxes with shuffled digits (they share no row, column or box, so any fill is valid), completes the grid with a randomized backtracking solver, then removes cells in random order. A removal is kept only if a solution counter, which stops as soon as it finds a second solution, still finds exactly one. Difficulty is set by the number of givens: 36 to 40 for Easy, 30 to 35 for Medium, 25 to 29 for Hard.

A post-workshop check extracted the generator and ran 300 puzzles per difficulty in Node 22. All 900 had exactly one solution, every solution was a valid grid consistent with its givens, and every givens count fell inside its range. Average generation time was 1.8 ms for Easy, 7.4 ms for Medium and 80 ms for Hard.

Play features cover what a regular Sudoku player expects: pencil-mark notes that clear themselves from peer cells when a digit is placed, live conflict highlighting across row, column and box, 20 steps of undo, a timer that pauses when the tab is hidden, a mistakes counter and completion percentage, per-digit counts on the number pad that disable a digit at 9/9, and auto-save to localStorage so a reload resumes the game.

Several details are not in the README. Sound is synthesized with the Web Audio API, with no audio files: short oscillator tones for placing, erasing and noting, a two-tone figure for a conflict, a pentatonic arpeggio when a row, column or box completes (pitched up for each extra group the same move completes), and a C-major chord on a win, behind a 25 ms throttle and a persisted mute toggle. Completing a group triggers a staggered wave animation through the Web Animations API. Colors are OKLCH tokens with system, light and dark themes.

Accessibility was in scope from the first commit: an ARIA grid with a label per cell, a polite live region for announcements, full keyboard play (arrow keys, 1–9, N for notes, Ctrl/Cmd+Z, Esc to pause), 44×44 px touch targets, and support for prefers-color-scheme and prefers-reduced-motion.

Five languages and Maya numerals

The localization commit added English, Spanish, Southern Quechua (Runa Simi), Central Nahuatl and Yucatec Maya. Strings live in a single I18N registry keyed by BCP-47 code. Adding a language is one entry; missing keys fall back to English, and the selector, the html lang attribute, ARIA labels, announcements and dialogs all update from it. The initial language comes from the browser, and the user's choice persists.

The more interesting decision is about numbers. The puzzle state is always the integers 1 to 9; only display and speech change per language. Maya has its own written numeral system, so in Yucatec Maya each digit renders in dot-and-bar notation built from DOM spans and CSS, with no font dependency:

const bars = Math.floor(n / 5); // each bar = 5const dots = n % 5; // 1 to 4 dots, drawn above the bars

The numeral is hidden from screen readers, which hear the Maya number word instead (jun, ka', óox and so on). Quechua and Nahuatl keep Western digits on the grid and use their number words for ARIA labels (huk, iskay, kimsa; cē, ōme, ēyi). The code comments give the reason: Inca quipu and Nahuatl's base-20 pictographs have no compact equivalent of the glyphs 1 to 9.

[@portabletext/react] Unknown block type "image", specify a component for it in the `components.types` prop

Infrastructure: private S3 behind CloudFront

The Terraform deploys the single file to a bucket that blocks all four forms of public access, enforces bucket-owner object ownership with ACLs disabled, enables versioning, and encrypts with SSE-S3 using a bucket key. The bucket name takes a random suffix so it is globally unique without coordination, and the object's ETag is the file's MD5, so any change to sudoku.html triggers a re-upload on the next apply.

CloudFront reaches the bucket through Origin Access Control, which signs every origin request with SigV4 and replaces the legacy Origin Access Identity. The bucket policy grants s3:GetObject to the CloudFront service principal only when AWS:SourceArn matches this distribution:

condition { test = "StringEquals" variable = "AWS:SourceArn" values = [aws_cloudfront_distribution.site.arn]}

The distribution redirects HTTP to HTTPS, uses the managed CachingOptimized policy with compression, serves IPv6, limits edge coverage to PriceClass_100, and maps 403 and 404 responses to the app. Input variables are validated (a regex on project_name, an allowed list for price_class), and default_tags stamp every resource with Project and ManagedBy.

The commit also includes two Mermaid diagrams of the deployed architecture: an architecture-beta version with AWS service icons, and a flowchart fallback for renderers without icon packs. Both have transparent backgrounds and come with instructions for importing into draw.io. The flowchart labels the live distribution ID.

What I would tighten next

  • Commit the Kiro artifacts. The repository has no .kiro directory, so the specs, steering, and hooks behind the build are not versioned. In a spec-driven workflow, the spec is what a reviewer reads first.
  • Add the tests the workshop taught. The generator's invariants (exactly one solution, givens within the difficulty range, givens consistent with the solution) map directly to property-based tests, and the 900-puzzle check above belongs in CI with a library such as fast-check.
  • Grade difficulty by technique. Clue count is a weak proxy: a 28-given puzzle can still fall to naked and hidden singles. Rating by the hardest technique a logical solver needs would make Hard mean hard.
  • Fix README drift. It does not mention localization, sound, themes, or Terraform, and its project tree predates the Terraform folder.
  • Harden the edge. Add a response headers policy for CSP and HSTS, a custom domain with an ACM certificate, access logs, a remote state backend, and a deploy step that runs the CloudFront invalidation instead of leaving it as a manual command.
  • Get native-speaker review of the Quechua, Nahuatl, and Maya strings before those languages ship beyond a demo.

Takeaways

Two hours of build time produced an accessible, multilingual app and least-privilege hosting for it. The single-file constraint did much of that work: with no build step, every change the agent made was immediately deployable, and the repository ships verification screenshots at six viewports in both themes.

The gaps follow a pattern. What came out fastest is what is easy to check by looking at it. What is missing (tests, the spec trail, accurate documentation) is what a second engineer needs before trusting the code. Those are the parts of Kiro's workflow the workshop spent its time on, and they are the next things to add to this repository.

Links