Six 2-week sprints, each shipping one or two complete deliverables. Not 20 weeks crammed into 2 — the right amount of work for the time. Each sprint is valuable on its own. Stop after any and you've shipped. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.5 KiB
Rose Ash: Fortnightly Sprints
Fit the task to the timescale, not vice versa.
The golden rule of project management. Don't take a 20-week plan and cram it into 2 weeks — that's denial. Ask instead: what's the highest-value thing you can finish — not start, finish — in two weeks?
Each sprint ships one or two deliverables. Both complete, both deployed, both making everything after easier. The master plan is still the map. You walk it two steps at a time.
Sprint 1: Sexp Content + Sexp Wire (Weeks 1-2)
Week 1: Posts become sexp
Ghost still runs for membership and newsletters. Don't touch that. Just the content path.
Deliverable: every post is sexp in the database, rendered through the sexp evaluator, editable through the existing editor.
- Lexical → sexp converter script (one-time migration)
- Add
body_sexpcolumn to Post model (Alembic migration) - Run converter against all posts
- Diff HTML output: Lexical render vs sexp render for every post
- Fix conversion gaps (embeds, cards, special blocks)
- Switch rendering pipeline:
post_data()readsbody_sexp - Editor save endpoint writes sexp directly to DB
- Native media upload endpoints (image, audio, files) — retarget from Ghost proxy
- Native OEmbed lookup endpoint
- Deploy, verify every page renders correctly
Week 2: Fragment endpoints return sexp
HTTP+HMAC stays as transport. The change is what travels over it.
Deliverable: fetch_fragment returns sexp trees instead of HTML strings. Callers render at the boundary.
- Relations service returns raw sexp (already renders from sexp — just stop calling
to_html()) - Blog renders relations sexp to HTML at the route level
- Events renders relations sexp to HTML at the route level
- Market renders relations sexp to HTML at the route level
- Callers can now filter, reorder, transform fragments before rendering
- Measure: is this faster, slower, or same as HTML fragments?
- Unit tests for sexp fragment round-trip (parse → filter → render)
Sprint 1 outcome
Posts are sexp. Internal fragments are sexp. Ghost still runs but only for membership/newsletters — the content path is clean. Everything that follows builds on this.
Sprint 2: Kill Ghost + New Relations (Weeks 3-4)
Week 3: Membership decoupling
Deliverable: all Ghost membership/newsletter infrastructure replaced with native equivalents.
- Rename
ghost_labels→labels, dropghost_id - Rename
ghost_tiers→membership_tiers, dropghost_id - Clean User model (drop
ghost_id,ghost_status,ghost_subscribed,ghost_note,ghost_raw) - Add
membership_tier_id,membership_statusto User - Native
subscriptionstable replacingghost_subscriptions - Wire Stripe directly on orders service (Checkout + Webhooks)
- Native newsletter model +
user_newsletters - Email sending via SMTP/SES
- Newsletter templates as sexp → email-safe HTML
- Post → email campaign workflow
Week 4: Delete Ghost + add entity relations
Deliverable: Ghost is gone. New entity relations are live.
- Delete
blog/bp/blog/ghost/directory - Delete
ghost_db.py,web_hooks/,ghost_admin_token.py,ghost_membership.py - Remove Ghost Docker service, all
GHOST_*env vars - Alembic migration: drop all
ghost_idcolumns - Rename model files:
ghost_content.py→content.py,ghost_membership_entities.py→membership.py - Add new
defrelations:post->post,market->product,calendar->calendar_entry,page->marketplace - Migrate
CalendarEntryPostjunction →ContainerRelation - Verify: entire platform runs with zero Ghost code
Sprint 2 outcome
Ghost is dead. Membership is native. New entity relations work. The platform is self-contained.
Sprint 3: Cart Split + Sexp Pages (Weeks 5-6)
Week 5: Cart microservices split
Deliverable: cart is thin CRUD. Likes, orders, and PageConfig are in their proper homes.
- Scaffold likes service (port 8009)
- Move PageConfig to blog service
- Orders service owns Order/OrderItem, checkout, Stripe/SumUp webhooks
- Cart becomes CartItem CRUD + checkout delegation
- Fragment cleanup: move remaining domain templates out of
shared/templates/
Week 6: Sexp page layouts
Deliverable: page layouts are sexp components, not Jinja template inheritance.
- Base page layout as sexp (head, nav, main, footer)
- Blog post page layout
- Market product page layout
- Event calendar page layout
- Component composition replaces
{% extends "base.html" %} - Content negotiation: same route returns HTML or sexp based on
Acceptheader
Sprint 3 outcome
Services are properly bounded. Pages are sexp top to bottom. The Accept: application/sexp header works — the door to client-side sexp is open.
Sprint 4: Internal Protocol (Weeks 7-8)
Week 7: Python sexp client/server library
Deliverable: working SexpConnection class, Quart handler, drop-in fetch_sexp().
- Wire format: length-prefixed sexp over Unix sockets
SexpConnection: connect, send sexp, receive sexp, handle streams- Quart integration: handler accepts sexp requests, returns sexp responses
fetch_sexp()— unified replacement forfetch_data()+fetch_fragment()- Error model:
(err :code 404 :message "not found") - Unit tests for protocol library
Week 8: Internal mesh migration
Deliverable: all inter-service communication runs on native sexp protocol.
- Blog ↔ relations on sexp
- Events ↔ relations on sexp
- Market ↔ relations on sexp
- Cart ↔ orders on sexp
- Account ↔ all services on sexp
- Benchmark: latency/throughput vs HTTP+HMAC
- HTTP kept as fallback for external/third-party calls
Sprint 4 outcome
The protocol is real. Running in production. Carrying every inter-service call. Battle-tested on real traffic.
Sprint 5: sexpr.js (Weeks 9-10)
Week 9: Core runtime
Deliverable: sexpr.js parses, renders, and mutates the DOM from sexp.
- Sexp parser in JS (<5KB gzipped)
- DOM renderer: sexp tree → DOM nodes
swap!,batch!,class!mutation primitivesrequest!— fetch sexp from server, apply mutations- Component system:
defcomp, slots
Week 10: Integration + caching
Deliverable: sexpr.js on every rose-ash page, with content-addressed caching.
<script src="sexpr.js">on every page — progressive enhancement- Partial page updates via sexp mutations (replaces full reload for nav, cart, likes)
- Content-addressed caching: hash component/post body, cache in localStorage
- Server sends hash manifest — unchanged content served from cache
- DevTools: sexp inspector in browser console
Sprint 5 outcome
The client speaks sexp. Pages load faster. Navigation doesn't reload. Components cache locally. Tier 1 is live.
Sprint 6: Federation + Stability (Weeks 11-12)
Week 11: AP over sexp
Deliverable: two rose-ash instances federate using sexp activities.
- Sexp-formatted activities (Create, Follow, Like, Accept)
- Federation test: two instances in Docker, full activity flow
- Identity verification over sexp (RSA signatures in sexp envelope)
- Cross-instance content rendering (sexp posts display correctly on remote instance)
Week 12: Harden + deploy
Deliverable: everything stable, tested, and deployed.
- Unit test coverage for all new code (protocol, converter, newsletter, likes)
- Deploy Tier 0 scalability (DB split, PgBouncer, Hypercorn workers)
- Bug sweep: end-to-end test every user flow
- Performance baseline: measure page load, inter-service latency, cache hit rates
- Documentation: update CLAUDE.md with new architecture
Sprint 6 outcome
Platform is stable, federated, performant, and documented. The protocol has been tested across network boundaries.
After Sprint 6: The 10%
These are real projects, not sprint tasks. Build them when they're needed:
- Rust client library + native client — when you want Tier 2 performance
- Browser extension — when sexpr.js proves the concept and you want Tier 1.5
- Client-as-node / IPFS / GPU mesh — when the cooperative network has multiple members with hardware
- Multi-cooperative commerce — when a second co-op actually exists and wants to federate
- Scalability Tiers 1-3 — when traffic hits Tier 0 limits
The Rhythm
Each sprint:
Monday-Thursday: Build. AI generates, you steer and test.
Friday: Deploy to dev, verify, fix what broke.
Weekend: Rest or think about next sprint.
Each week:
One deliverable. Finished. Deployed. Working.
12 weeks, 12 deliverables, each one complete. Not 20 weeks of partial progress. Not 2 weeks of rushed everything. The right amount of work for the time available.
Sprint Schedule
Sprint 1 (W1-2): Sexp content + sexp wire ← content path is clean
Sprint 2 (W3-4): Kill Ghost + new relations ← platform is self-contained
Sprint 3 (W5-6): Cart split + sexp pages ← services bounded, pages are sexp
Sprint 4 (W7-8): Internal sexp protocol ← protocol is real and running
Sprint 5 (W9-10): sexpr.js client runtime ← client speaks sexp
Sprint 6 (W11-12): Federation + stability ← federated and stable
Each sprint builds on the last. Each is valuable on its own. If you stop after any sprint, you've shipped something real.