sports / scoreboard

sports.dancroak.com is a scoreboard and a news reader. I built it with Instinct, a personal AI assistant, using text messages over two days.

I texted instructions from my phone. The assistant implemented the code and deployed the service.

What it shows

The front page lists games across the NFL, college football, MLB, EPL, and Champions League. Each row shows the teams, the start time or score, and a win probability.

The win probability is the Kalshi market price. I do not run a prediction model. The market price updates when new information arrives.

A green dot marks a live game. A gray dot and "Final" mark a completed game. The whole row links to the game page for a large tap target.

A second page displays sports news. The app ingests RSS feeds into SQLite. The assistant writes article summaries every 30 minutes.

Text messages as the interface

I directed the build over iMessage while the assistant typed. I sent screenshots when a layout looked incorrect. The assistant modified the templates and deployed updates.

I made the product decisions, such as removing clutter and setting tap targets. The assistant made the implementation choices.

A phone interface prevents micromanagement. I cannot inspect code diffs easily on a phone. I inspect the live site and report defects. The assistant identifies the cause and fixes the bug. I review the visible outcome rather than the code change.

What autonomous operation needs

Autonomous operation requires four components:

Where I overruled the assistant

The assistant initially proposed a different network design. It asked to generate an SSH keypair, store the private key in its vault, and expose port 22 publicly.

The assistant optimized for autonomous access. I optimized for containment and recovery when the assistant fails.

A private key on an external system increases attack surface. An open SSH port attracts internet traffic. I rejected the public port and required Tailscale. This choice shows why human oversight remains necessary for agent workflows.

The stack

The application uses one Go binary and hml templates. SQLite stores the articles and team metadata. The application opens SQLite in WAL mode on a single connection:

db, err := sql.Open("sqlite3", dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL&_foreign_keys=ON")
db.SetMaxOpenConns(1)

The web server and the ingest process write to the same database file. A single connection prevents internal locks, and _busy_timeout handles write contention. I explain these database options in sqlite.

Caddy terminates TLS and proxies traffic to the application:

sports.dancroak.com {
        reverse_proxy 127.0.0.1:8081
}

systemd manages the application process with Restart=always. cron executes the ingest command every 30 minutes.

The VM

The assistant initially served traffic from its sandbox through a Tailscale funnel. The sandbox reset after 20 hours and broke the site. Development sandboxes cannot host persistent services.

I migrated the app to an Ubuntu 24.04 VM on Ubicloud. It costs $14 per month. The firewall permits ingress on ports 80 and 443 only. SSH access requires Tailscale.

Before you compile the application on Ubuntu, install gcc. The go-sqlite3 driver requires cgo. Without cgo, the build produces a broken binary.

Git stores the code and deploy scripts for quick recovery. SQLite data lives on the VM disk without snapshots. If the VM fails, I provision a new instance and rerun ingest.

Operational gaps

The assistant operates the site autonomously, but blind spots remain:

The assistant must learn to detect stale data and failed jobs proactively. An assistant that writes code provides value. An assistant that maintains production reliability provides more value.

Value

The site is fast and personal. It displays the data that I want without clutter. Building the application required two text messaging sessions and an inexpensive virtual machine.

← All articles