← Back to Plan

Agents

Steel Notes — Agent Guide

Important: Required Agent Behaviors

1. Pull latest main before starting work. Run git pull origin main to get the current docs/PLAN.md and architecture docs. Plans change frequently — never work from a stale copy.

2. Update docs/PLAN.md after completing any task — mark it ✅, 🚧, or ⬜.

3. Add new tasks to docs/PLAN.md when the user's prompt or the work itself requires steps not already tracked. Place them in the appropriate phase and section.

4. Add to docs/HUMAN_TASKS.md any step that requires a human: running commands on real hardware, creating accounts, provisioning API keys, configuring third-party services, app store setup, DNS, etc. Do not block on these — log them and move on.

5. All doc changes go directly to main — no feature branches for documentation.

Tech Stack

LayerTechnologyNotes
**Shared business logic**Kotlin Multiplatform (KMP)shared/ — targets iOS, macOS, Android
**iOS/macOS app**SwiftUI + KMP frameworkiosApp/
**macOS CLI**Kotlin/Native (Clikt)cli/
**Backend API**Rust 2024, Axum 0.8server/ — deployed to AWS Lambda via cargo-lambda
**Database (server)**Postgres on RDS t4g.nanosqlx with compile-time checked queries
**File storage**AWS S3Vault files stored at {user_id}/{type}/{file}.md
**Push notifications**AWS SNS → APNs (iOS/macOS), FCM (Android future)Silent push to trigger background sync
**AI (LLM + Vision)**Claude on AWS Bedrockaws-sdk-bedrockruntime — classify, structure, enrich, OCR. IAM auth, no API keys.
**Voice transcription**Amazon Transcribeaws-sdk-transcribe — async batch, reads audio from S3. Replaces Whisper.
**Infrastructure**AWS (Lambda, API Gateway, RDS, S3, SNS, Bedrock, Transcribe)Terraform in server/deploy/terraform/
**Local DB (client)**SQLite via SQLDelightIndex is disposable — rebuilt from .md files
**HTTP client (KMP)**Ktor 3.0.3Used for both sync API calls and direct S3 transfers
**Auth**Email/password + Sign in with Apple → JWTMulti-provider, account linking. See docs/AUTH_ARCHITECTURE.md

See docs/TECH_DECISIONS.md for the full rationale behind each decision (why S3 over iCloud, why Rust over Go/Kotlin, why AWS over Azure, etc.).


Quick Start

Prerequisites

  • **JDK 17**: brew install openjdk@17
  • **Xcode 16+**: Required for iOS/macOS compilation
  • Set JAVA_HOME before running Gradle: export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
  • Build Commands

    ```bash

    Build shared KMP module (all Apple targets)

    ./gradlew :shared:build

    Compile just macOS arm64 (fastest for iteration)

    ./gradlew :shared:compileKotlinMacosArm64

    Build CLI executable

    ./gradlew :cli:linkDebugExecutableMacosArm64

    CLI binary location

    ./cli/build/bin/macosArm64/debugExecutable/steel.kexe

    ```

    Running Tests

    ```bash

    Set JAVA_HOME (use the JDK available on your system)

    export JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home # macOS

    export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 # Linux/CI

    Run all shared module tests (JVM — works on any platform)

    ./gradlew :shared:jvmTest

    Run a specific test class

    ./gradlew :shared:jvmTest --tests "com.steelnotes.editor.FormattingEngineTest"

    Run native tests (requires macOS with Xcode)

    ./gradlew :shared:macosArm64Test

    Run all tests across all available targets

    ./gradlew :shared:allTests

    View HTML test report

    open shared/build/reports/tests/jvmTest/index.html

    ```

    Test architecture: Tests live in shared/src/commonTest/ and run on all targets. A JVM target is configured specifically to enable running tests on Linux and CI without Xcode. JVM actual implementations for expect declarations live in shared/src/jvmMain/.

    Current test suites:

    SuiteFileTests
    MarkdownDocumentParsereditor/MarkdownDocumentParserTest.ktBlock parsing, inline span parsing
    MarkdownDocumentSerializereditor/MarkdownDocumentSerializerTest.ktRound-trip (parse → serialize) fidelity
    FormattingEngineeditor/FormattingEngineTest.ktToggle bold/italic/code/highlight, headings, links, lists, blockquotes
    MarkdownDocumenteditor/MarkdownDocumentTest.ktModel rawText serialization, document text property

    CLI Usage

    ```bash

    steel --vault /path/to/vault init

    steel --vault /path/to/vault create source --title "Title" --author "Author" --tags "t1,t2" --body "..."

    steel --vault /path/to/vault list [--type note|source|thought|question]

    steel --vault /path/to/vault search "query" [--type source]

    steel --vault /path/to/vault read <note-id>

    steel --vault /path/to/vault delete <note-id>

    steel --vault /path/to/vault index

    steel --vault /path/to/vault --json list # JSON output for programmatic use

    ```

    Default vault: ~/.steel-notes/vault


    Architecture

    Project Structure

    ```

    steel-notes/

    ├── shared/ # KMP shared Kotlin module (all business logic)

    │ └── src/

    │ ├── commonMain/ # Platform-agnostic code

    │ ├── appleMain/ # Apple platform actuals (iOS + macOS)

    │ ├── iosMain/ # iOS-specific code

    │ └── macosMain/ # macOS-specific code

    ├── cli/ # Kotlin/Native CLI executable

    │ └── src/

    │ ├── commonMain/ # CLI commands (Clikt)

    │ └── macosMain/ # macOS platform glue (getenv etc.)

    ├── iosApp/ # SwiftUI iOS/macOS app (not yet implemented)

    ├── gradle/ # Version catalog (libs.versions.toml)

    └── AGENTS.md # This file

    ```

    Key Packages (shared module)

  • com.steelnotes.model — Data models: VaultItem (sealed interface), Note, Source, Thought, Question. See docs/NOTE_ARCHITECTURE.md
  • com.steelnotes.parserFrontmatterParser, WikilinkExtractor, TransclusionExtractor, TransclusionResolver, AnnotationParser, NoteSerializer, NoteFactory
  • com.steelnotes.db — SQLDelight generated code: SteelDatabase, SteelNotesQueries
  • com.steelnotes.searchSearchEngine (FTS5 + BM25), SearchResult, SearchFilter
  • com.steelnotes.vaultVaultManager (CRUD), VaultIndexer, PlatformFileSystem (expect/actual)
  • com.steelnotes.platformDatabaseDriverFactory (expect/actual)
  • Data Flow

    1. .md files with YAML frontmatter are the source of truth

    2. FrontmatterParser + NoteFactory convert files → typed Note objects

    3. VaultManager writes notes to disk AND inserts into SQLite index

    4. SearchEngine queries FTS5 for full-text search

    5. VaultIndexer rebuilds the entire SQLite index from vault files

    File Format

    Every .md file has YAML frontmatter with --- delimiters. The type and id fields are required.

    See /Users/ethanpitts/Downloads/steel-notes-spec.md for full schema.


    Pitfalls & Gotchas

    Swift / iOS

    See docs/SWIFT_PITFALLS.md for the comprehensive list of Swift, SwiftUI, KMP interop, XcodeGen, and simulator pitfalls. Read it before touching iosApp/. Every entry caused a real build failure or runtime crash.

    Gradle / Build

  • **System Gradle is 9.4.1** — but SQLDelight 2.0.2 requires Gradle 8.x. Always use ./gradlew (wrapper, pinned to 8.10), never the system gradle command.
  • **JAVA_HOME must be set** to JDK 17 before every ./gradlew invocation. The system may default to a different JDK.
  • **First build downloads Kotlin/Native toolchain** (~500MB). Subsequent builds are fast.
  • Kotlin Multiplatform

  • **applyDefaultHierarchyTemplate()** creates appleMain, iosMain, macosMain source sets automatically. Don't manually dependsOn() — it conflicts with the template.
  • **expect/actual classes** produce warnings about being in Beta. Suppressed via kotlin.suppressExpectActualClasses=true in gradle.properties.
  • **ExperimentalForeignApi** opt-in required for all Apple platform interop code (NSFileManager, NSString, posix). Add @file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) at the top of files.
  • **Native cache disabled** for macOS targets (kotlin.native.cacheKind.macosArm64=none) due to linker issues with the current Kotlin/Native version.
  • SQLDelight

  • **SQLite 3.18 dialect** — NULLS FIRST/NULLS LAST not supported. Use CASE WHEN x IS NULL THEN 0 ELSE 1 END instead.
  • **FTS5 queries** return generated types (e.g., SearchNotes, SearchNotesFiltered) that are different from the Notes table type. You can't use one where the other is expected.
  • **-lsqlite3 linker flag** required for any executable or framework that uses SQLDelight's native driver. Add via linkerOpts("-lsqlite3") in build.gradle.kts.
  • Clikt (CLI framework)

  • **Clikt 5.x** removed the help parameter from constructors (CliktCommand, option(), argument()). Use the builder pattern instead if help text is needed.
  • **argument() names** are positional — don't pass help = as a named parameter.
  • Files & Vault

  • **Vault path convention**: {vaultRoot}/{type.directory}/{slug}.md — e.g., sources/beyond-good-and-evil.md
  • **ID pattern**: {prefix}-{YYYY-MM-DD}-{sequence} — e.g., src-2026-03-20-001
  • **SQLite index is disposable** — it can always be rebuilt from the .md files via steel index

  • Sync Architecture

    Cross-device sync is designed but not yet implemented. See docs/SYNC_ARCHITECTURE.md for the full design.

    Key points for implementers:

  • Sync uses **S3/R2 + custom REST API** — not iCloud Drive or CloudKit
  • **File bytes never touch the API** — clients upload/download directly to S3 via presigned URLs
  • The API is a thin coordination layer: auth, version tracking (Postgres), presigned URL generation, push notifications
  • **Auth: Sign in with Apple** (required for App Store) → JWT access tokens
  • **SQLite index is never synced** — rebuilt locally after pulling remote file changes
  • **Conflict detection is version-based** — POST /vault/push rejects if base_version doesn't match server. Client downloads the server version and runs ConflictResolver
  • Conflict resolution is **frontmatter-aware** — YAML fields merge independently from body text
  • **Push notifications (APNs)** notify other devices when a sync completes — near real-time sync without polling
  • Sync metadata lives in .steel/sync.db (separate from the disposable index.db)
  • New packages: com.steelnotes.sync (SyncManager, SyncClient, ChangeDetector, ConflictResolver) and com.steelnotes.auth (AuthManager, TokenStore)
  • Swift side: AuthService.swift, SignInView.swift, SyncService.swift, ConflictResolutionView.swift, SyncSettingsView.swift
  • **Backend:** Rust (Axum + sqlx + aws-sdk-s3), deployed to AWS Lambda. Code lives in server/ directory (monorepo)
  • **AWS infra:** Lambda + API Gateway (API), RDS Postgres (state), S3 (vault files), SNS (push). Terraform in server/deploy/terraform/

  • Plan Tracker Site (dev.pittsventures.com)

    A static dashboard at plan/index.html is auto-generated from PLAN.md and docs/HUMAN_TASKS.md. It shows implementation progress by phase with expandable task details.

    How it works

  • **plan/build.py** — Python script that parses markdown files, extracts task statuses (✅/🚧/⬜ and [ ]/[x] checkboxes), and generates plan/index.html + plan/data.json
  • **.github/workflows/deploy-plan.yml** — GitHub Actions workflow that runs build.py and syncs plan/ to an S3 bucket on every push to main
  • **Hosting:** S3 static website + CloudFlare DNS (CNAME dev → S3 endpoint)
  • Local preview

    ```bash

    python3 plan/build.py

    open plan/index.html

    ```

    When to rebuild

    The site rebuilds automatically on push to main. If you update PLAN.md or docs/HUMAN_TASKS.md, the next merge to main will update the site.


    Current Priorities (in order)

    1. Cloud Vault Sync (Phase 2) — S3 + Rust/Axum backend + KMP sync client. See docs/SYNC_BUILD_GUIDE.md for the actionable build plan and docs/SYNC_ARCHITECTURE.md for the full design spec.

    2. Capture Layer & AI Parsing (Phase 3) — Share Sheet, URL → structured Source, AI-powered classification and transposition. Spec TBD.

    3. iOS UI Polish (Phase 4) — Reader, Editor, Search views. Intentionally delayed.

    What's Not Yet Implemented

  • Cloud vault sync — Rust/Axum API + S3 + KMP client (Phase 2, **next up**)
  • User accounts / Sign in with Apple (part of Phase 2A)
  • Share Sheet extension (Phase 3A)
  • AI client + TranspositionEngine (Phase 3B)
  • iOS Reader/Editor/Search views (Phase 4)
  • SKIE bridging for Kotlin Flow → Swift async/await
  • Question lifecycle engine (resurfacing, transitions)
  • Synthesis generation
  • PDF reader
  • Idea graph visualization
  • macOS SwiftUI app
  • steel update CLI command