[hero_team_box] Hero Team Box — the team's shared dev environment (story + v1 spec) #232

Closed
opened 2026-05-10 02:10:13 +00:00 by mik-tf · 2 comments
Owner

Hero Team Box — the team's shared dev environment

Story format. General → particular. Executive summary first; everything an AI would need to code v1 follows.


Executive Summary

A single shared Hero box where every developer gets a Linux user slot with a fully-provisioned ~/hero, develops on the box itself via mosh + tmux, and binds each service to a specific (repo, branch) they care about. Pushes trigger automatic rebuilds in the matching slot(s). Every successful build publishes a content-hashed archive as a Forgejo release on the source repo, so any other slot — or any external machine — can run that exact binary without compiling. Team membership lives in a new GitOps repo (lhumina_code/hero_team); per-user secrets stay in each dev's existing private secrets repo. Claude credentials follow a two-layer model: personal claude login per user (optional) plus an org pool of N Claude Max keys assigned to users declaratively via hero_team.

The point: today five people each fight their own laptop. Tomorrow five people share one canonical Hero, see each other's running services through per-user URLs, and the integration slot continuously builds development so the team always knows whether the trunk works.

This is v1. Single host (DO droplet, validated end-to-end). Multi-machine farms are explicitly v2.


Why this matters — the "5 > 0" framing

Today: every dev fights their own machine. Setup drift, version skew, "works on my laptop." Five Heros, zero shared ground.

Tomorrow: one box, five user slots, one Hero. SSH in, you have a working environment. See someone else's running service in your browser. Merge to development, see it picked up by the integration slot. Lose the box, spin a new one in 30 minutes from a forge-tracked manifest. The shared box converts the team from N parallel single-player setups into one cooperative environment.

The build automation is plumbing. The shared environment is the prize.


What this supersedes

This story is the v1 elaboration of home#121 — HeroOS Development Environment Setup, Mahmoud's v0 runbook for the current shared box (manual user creation, init sync, service_complete --update, a 2, manual secrets setup). #121 stays open as the operational runbook until v1 lands; the bugs Sameh filed at hero_skills#106–112 are explicitly part of v1's acceptance.

The v1 upgrade:

  • Manual useradd → GitOps via lhumina_code/hero_team
  • service_complete (broken per hero_skills#106) → hero_dev start (subsumes + fixes)
  • Implicit per-host shared state → explicit per-slot manifest binding (service, repo, branch)
  • Local-only builds → builds publish content-hashed Forgejo releases automatically
  • Ad-hoc Claude key handling → two-layer model (personal + org pool) with declarative assignment
  • "What's everyone running?" answered by Slack → answered by hero_dev peek and the fleet view at /admin

The complete vision — UX by role

Joining the team (new hire flow)

Mik opens a PR against lhumina_code/hero_team adding users/sara.toml:

name              = "sara"
ssh_pubkeys       = ["ssh-ed25519 AAAA..."]
groups            = ["developers", "hero"]
branch_suffix     = "sara"
manifest_template = "developer"

[claude_org_key]                          # optional — present if org assigns her a key
enabled         = true
key_secret_name = "ORG_CLAUDE_KEY_2"

After merge, the hero_team_sync daemon on the box reconciles: Sara's Linux account is created, SSH keys installed, groups set, ~/hero is provisioned from the developer template (pinning core services to development from: release), her private secrets repo lhumina_code-private/secrets_sara is created on forge via API.

Sara installs mosh on her laptop. Runs mosh sara@team.hero.box. First-login script prompts for her Forge token, runs secrets_sync to clone her secrets repo, opens secrets.toml for her to fill in API keys, runs secrets push. Then either:

  • If she's pre-assigned an org Claude key (per the block above), she sees a message: "You've been assigned ORG_CLAUDE_KEY_2a 2 is ready to use."
  • Otherwise the script runs claude login so she can use her personal account.
  • Or both — she does claude login AND has an org key; she can toggle later via hero_dev claude use.

Then hero_dev status shows her stack running, with reachable URLs:

https://team.hero.box/sara/proc/admin
https://team.hero.box/sara/router/admin
https://team.hero.box/sara/slides
https://team.hero.box/sara/claude

Total elapsed: ~10 minutes. She's productive.

Daily flow (existing dev)

Alice mosh-ins, lands in her tmux session (preserved across mosh disconnects). Runs hero_dev status:

hero_proc      → development         @ 7c6b5a4   running   from-release
hero_router    → development         @ 9f8e7d6   running   from-release
hero_slides    → development_alice   @ a1b2c3d   running   from-build
hero_claude    → development         @ b4c5d6e   running   from-release   [org-key-1]

She works on hero_slides in ~/hero/code/hero_slides/worktrees/development_alice/ (worktree created earlier by hero_dev switch). Edits, commits. Runs hero_dev rebuild hero_slides — hero_builder runs, binary swaps, hero_proc restarts. Refresh https://team.hero.box/alice/slides; change is live.

When happy, pushes development_alice to forge, opens a PR. Once merged to development, the integration slot rebuilds. Everyone with hero_slides @ development from: release automatically gets the new binary on next reconcile (or hero_dev pull).

Testing someone else's branch

hero_dev switch hero_router development_mik --from release

Pulls Mik's already-published archive from forge (no compile), swaps Alice's hero_router binary, restarts. To revert: hero_dev switch hero_router development --from release.

If Alice wants to compile it instead: --from build. A worktree is created at ~/hero/code/hero_router/worktrees/development_mik/, hero_builder compiles, hero_proc restarts.

Peeking at the team

hero_dev peek thabet

Shows Thabet's manifest + state. https://team.hero.box/thabet/slides shows what he's running. No screen-shares, no "what version" Slack threads.

Running an agent

a 2 spawns a hero_claude_rust session in Alice's slot at effort level 2 (alias for hero_dev agent --effort 2). It uses whichever Claude credential the resolution order picks — see §Specifications/AI API key model. Plan mode, Ralph loop, Forgejo-issue auto-clone-and-comment per existing hero_claude_rust surface, scoped to her user.

Integrator's view (Mik)

https://team.hero.box/admin — augmented hero_codescalers_admin. Grid of users × services. Integration row at the top: green = development is shippable. Each cell shows branch, hash, build status, run status, last-rebuild-at. Plus a per-user "claude credential" column showing org-key-N, personal, or not configured. Click integration row → latest forge release tags + archive download links. One screen, full-stack truth.

Founder / visitor

Same dashboard. Sees who's working on what, whether the merged stack works, what's been released, who's using which Claude key.

When the box dies

Spin a new DO droplet, run bootstrap_droplet_source.sh (s84-validated per home#230), point at lhumina_code/hero_team. Reconciler recreates every user account, SSH keys included; each user's ~/hero provisions from template; secrets repos pull fresh on first login. Org Claude keys are restored from the driver's hero_aibroker secret store (separate backup discipline — see Open Questions). Team back online in ~30 minutes. The only thing not preserved is unpushed work in worktrees — exactly what nobody should be holding hostage on a single machine.


Architecture

DO droplet (or Hetzner / any commodity x86_64 Ubuntu)
host: team.hero.box
│
├── driver account
│   ├── hero_router            ← single TCP entry point; /:user/:service/... routing
│   ├── hero_codescalers       ← coordinator; build.submit RPC; user.create/adopt
│   ├── hero_aibroker          ← org AI key vault (incl. ORG_CLAUDE_KEY_*)
│   ├── hero_team_sync (NEW)   ← reconciles lhumina_code/hero_team into the box
│   └── hero_proc              ← driver's own supervisor
│
├── user: alice
│   ├── ~/hero/cfg/dev_manifest.toml            ← her bindings + claude.preference
│   ├── ~/hero/code/<repo>/worktrees/<branch>/  ← per-branch git worktrees
│   ├── ~/.config/claude/                       ← personal claude login (if done)
│   ├── hero_proc, hero_router, hero_slides, hero_claude_rust, ...
│   └── reachable at https://team.hero.box/alice/...
│
├── user: thabet ...
├── user: mik    ...
├── user: sara   ...
│
└── user: integration
    ├── manifest pins everything to development from: build
    └── reachable at https://team.hero.box/integration/...

External (forge.ourworld.tf):
├── lhumina_code/<each repo>             ← code + per-build-hash forge releases (artifacts)
├── lhumina_code/hero_team (NEW)         ← team membership + manifest templates (GitOps)
└── lhumina_code-private/secrets_<user>  ← per-user private secrets (one repo each)

Data flow — push to forge, integration slot rebuilds

1. Mik squash-merges PR to lhumina_code/hero_router development
2. forge fires webhook → team.hero.box (https://team.hero.box/hooks/forge)
3. hero_router (driver) routes to hero_codescalers
4. hero_codescalers reads every slot's manifest:
   - alice's slot: hero_router → development from: release  → MATCH (release-following)
   - mik's slot:   hero_router → development_mik             → no match
   - integration:  hero_router → development from: build     → MATCH (will rebuild)
5. Dispatches:
   a. integration slot → nu_exec → cd worktree → hero_builder --release --publish
      → publishes hero_router-development-<hash>.tar.gz as forge release
      → hero_proc service restart hero_router
   b. alice's slot → hero_dev pull hero_router (next reconcile tick)
      → fetches new release archive → swap binary → hero_proc restart
6. Posts result back as commit comment on the merge commit

Data flow — push to hero_team

1. Anyone PRs against lhumina_code/hero_team (add user, change template, rotate key)
2. After merge, forge fires webhook → hero_team_sync
3. Reconciles:
   - new users → hero_codescalers user.create + provision ~/hero from template
                 + Forgejo API: create lhumina_code-private/secrets_<user>
   - updated SSH keys → rewrite ~<user>/.ssh/authorized_keys
   - updated [claude_org_key] → no-op on the box; the assignment is consulted live
                                 by hero_aibroker on each session spawn
   - removed users → deactivate slot (chage -E 0); snapshot ~/hero to archive dir
   - template changed → seed for FUTURE users only; existing manifests untouched
4. Updates last_synced_commit; posts commit-comment with reconcile result.

Data flow — Claude session spawn

1. Alice runs `a 2` (alias for hero_dev agent --effort 2)
2. hero_dev resolves the credential per the order (preference > org > personal > error):
   - reads alice's dev_manifest.toml [claude.preference]
   - if set to "org" or "auto", calls aibroker.claude_key.get({ user: alice, slot: ... })
   - hero_aibroker authorizes by re-reading hero_team's users/alice.toml
   - returns key in-memory only (never persisted in alice's slot)
3. hero_claude_rust spawns the `claude` CLI subprocess with that credential
4. stream-json events flow back; alice sees output live

Data flow — local rebuild in a slot

1. Alice edits in ~/hero/code/hero_slides/worktrees/development_alice/
2. Commits in that worktree
3. (a) auto_rebuild = false (default): nothing until she runs hero_dev rebuild
   (b) auto_rebuild = true: post-commit hook triggers hero_dev rebuild
4. hero_dev calls hero_codescalers.build.submit
   { user: alice, repo: hero_slides, branch: development_alice, from: build }
5. → hero_builder compiles in the worktree → installs to ~alice/hero/bin/
   → hero_proc restart hero_slides
6. (Optional) push to forge → integration slot reaction per the first flow

The binary / release layer

This is the structural piece that makes "5 > 0" durable.

Every hero_builder run does two things:

  1. Installs the binary into that user's ~/hero/bin/ for their running services.
  2. With --publish: packages a content-hashed archive and publishes it as a Forgejo release on the source repo. Tag format: <branch>-<short-hash> (e.g. development_mik-a1b2c3d). Archive: <binary>-<platform>.tar.gz (e.g. hero_router-linux-musl-x86_64.tar.gz).

This single mechanism delivers:

  • No-compile collaboration: pull, don't build (--from release).
  • Automatic CI artifacts: every merge to development produces a release.
  • Reproducibility: hash-pinned manifests; same binary everywhere.
  • musl static binaries via cross: per hero_builder SPEC §6.5/6.6. One binary on any glibc/musl Linux of the matching arch.
  • Multi-platform from one box: same release tag carries linux-musl-x86_64, linux-musl-arm64, macos-arm64, etc.

The build IS the release. No separate CI/CD.


Specifications

Dev manifest — ~/hero/cfg/dev_manifest.toml

Source of truth for what a single slot is running. Owned by the user (the developer template seeds it on user.create; never overwritten by reconciler).

[[bind]]
service       = "hero_slides"
repo          = "lhumina_code/hero_slides"
branch        = "development_alice"
from          = "build"        # build | release | hash
auto_rebuild  = false          # auto-rebuild on commit in worktree (opt-in)

[[bind]]
service       = "hero_router"
repo          = "lhumina_code/hero_router"
branch        = "development"
from          = "release"      # pull pre-built artifact (latest matching <branch>-*)

[[bind]]
service       = "hero_proc"
repo          = "lhumina_code/hero_proc"
branch        = "development"
from          = "hash"
hash          = "a1b2c3d4"     # pin to exact release archive

# User-level Claude credential preference (see §AI API key model)
[claude]
preference    = "auto"         # auto | personal | org

# Per-slot state (managed by hero_dev; user shouldn't edit by hand)
[state.hero_slides]
last_build_hash   = "a1b2c3d"
last_build_status = "ok"
last_build_at     = "2026-05-09T14:30:12Z"
last_run_status   = "running"

Worktree convention: each binding implies ~/hero/code/<repo-basename>/worktrees/<branch>/. The reconciler ensures the worktree exists; hero_dev operates inside it.

lhumina_code/hero_team repo schema

hero_team/
├── README.md                       (schema, PR workflow, reconcile semantics)
├── users/
│   ├── alice.toml
│   ├── mik.toml
│   ├── thabet.toml
│   ├── timur.toml
│   └── integration.toml
└── manifest_templates/
    ├── developer.toml              (services pinned to development from: release)
    └── integration.toml            (services pinned to development from: build)

users/<name>.toml:

name              = "alice"           # MUST match Linux username
ssh_pubkeys       = ["ssh-ed25519 AAAA..."]
groups            = ["developers", "hero"]   # "hero" required for inter-user socket access
branch_suffix     = "alice"           # convention: dev branches are development_<suffix>
manifest_template = "developer"       # which manifest_templates/<name>.toml seeds the slot
display_name      = "Alice Smith"     # optional, for fleet view
email             = "alice@..."       # optional
deactivated       = false             # true to deactivate without deleting

# Optional: org-pool Claude key assignment (see §AI API key model)
[claude_org_key]
enabled          = true
key_secret_name  = "ORG_CLAUDE_KEY_1"               # singular form (sugar)
# OR for multi-key (v1.x):
# key_secret_names = ["ORG_CLAUDE_KEY_1", "ORG_CLAUDE_KEY_2"]

manifest_templates/<role>.toml is a [[bind]] list, same schema as dev_manifest.toml minus [state.*] blocks.

hero_dev CLI surface

hero_dev status                        # show all bindings + state + claude credential
hero_dev sync                          # alias for old `init sync` — pull repo updates
hero_dev start                         # start all services per manifest
hero_dev stop                          # stop all services
hero_dev restart [<service>]           # restart all or one
hero_dev switch <service> <branch> [--from build|release|hash]
                                       # rebind; ensure worktree; (build or pull); restart
hero_dev bind <service> <repo> <branch> [--from ...]
                                       # add a NEW row to manifest
hero_dev unbind <service>              # remove a row
hero_dev rebuild [<service>]           # force rebuild + restart (from: build only)
hero_dev pull [<service>]              # for from:release rows, fetch latest matching archive
hero_dev peek <user>                   # show another user's manifest + state
hero_dev fork <repo>                   # create development_<my-suffix> branch on forge,
                                       #   bind locally, switch to from: build
hero_dev logs <service> [-f]           # tail hero_proc logs for a service
hero_dev agent [--effort N] [--prompt ...]
                                       # spawn hero_claude_rust session; alias: `a N`
hero_dev claude use {personal|org|auto}
                                       # set claude credential preference for this slot
hero_dev claude status                 # show which credential is active + key tail
hero_dev doctor                        # diagnose: socket reachability, hero_proc liveness,
                                       #   forge token validity, secrets-repo presence,
                                       #   claude credential reachable, hero_aibroker reachable

Implementation notes:

  • Calls hero_codescalers.build.submit for cross-slot operations; hero_proc directly for local restart.
  • --from release resolves to latest release matching <branch>-* (lexical sort by hash); --from hash <h> pins exactly.
  • hero_dev fork requires FORGEJO_TOKEN (read from secrets repo).
  • Cross-user fan-out always through hero_codescalers, never direct.
  • a is a shell alias forwarding to hero_dev agent.

hero_codescalers.build.submit RPC

build.submit({
  user:       string,             # target slot's Linux username
  repo:       string,             # e.g. "lhumina_code/hero_router"
  branch:     string,
  from:       "build" | "release" | "hash",
  hash:       string?,            # required if from="hash"
  publish:    bool = false,       # true → push archive to forge release on success
  restart:    bool = true,        # true → hero_proc restart after build/pull
}) -> {
  ok:          bool,
  build_hash:  string,
  duration_ms: u64,
  log:         string,            # truncated; full log in hero_codescalers job record
  job_id:      string,
}

Implementation: dispatches a nu_exec job into the target user's hero_proc with tag build-<repo>-<branch>. Job runs hero_dev sync then hero_builder --release [--publish] in the worktree, then hero_proc service restart <name> if requested.

hero_team_sync daemon contract

Lifecycle: long-running daemon under driver account, supervised by driver's hero_proc.
Inputs:
  - HTTP webhook endpoint at /team-sync/webhook (forge POSTs here on hero_team push)
  - 5-minute periodic poll fallback
  - hero_codescalers RPC client
  - Forgejo API client (secrets-repo creation)
State:
  - last_synced_commit (in driver's ~/hero/var/team_sync/state.toml)
  - reconcile log (append-only at ~/hero/var/team_sync/reconcile.log)
Reconcile rules:
  1. Fetch hero_team @ development.
  2. Diff against last_synced_commit.
  3. New users/<name>.toml:
     - hero_codescalers.user.create({name, ssh_pubkeys, groups})
     - Provision ~/hero from manifest_templates/<template>.toml
     - Forgejo API: create lhumina_code-private/secrets_<name> if not exists
     - Add starter secrets.toml.example
  4. Modified users/<name>.toml:
     - ssh_pubkeys changed → rewrite ~<name>/.ssh/authorized_keys
     - groups changed → usermod -G ...
     - manifest_template changed → WARN ONLY (do not clobber existing manifest)
     - [claude_org_key] changed → no box-side action; consulted live by hero_aibroker
     - deactivated=true → disable login (chage -E 0); do not delete
  5. Removed users/<name>.toml:
     - Same as deactivated=true
     - Snapshot ~<name>/hero to ~/hero/var/team_sync/archive/<name>-<timestamp>.tar.gz
  6. Update last_synced_commit; commit-comment on merge with reconcile result.
Idempotence: every operation idempotent; re-run with same state is a no-op.
Failure: per-user errors logged but don't block other users; top-level errors leave
         last_synced_commit unchanged so next tick retries.

URL routing convention

hero_router routes https://<host>/<user>/<service>/... to ~<user>/hero/var/sockets/hero_<service>/web.sock (or ui.sock per service convention). Already supported via the hero_web_prefix skill; this story makes it the default for every user-provisioned service.

The <service> prefix in the URL is the short name (e.g. slides, not hero_slides).

Worktree layout convention

~/hero/code/<repo-basename>/                        ← bare or main checkout
~/hero/code/<repo-basename>/worktrees/<branch>/     ← one worktree per active branch

hero_dev switch <service> <branch> ensures the worktree exists, then operates inside it. Branch-switching never destroys uncommitted work because each branch gets its own directory.

forge worktree (per home#121) is the underlying mechanism; hero_dev is the higher-level UX on top.

hero_builder --publish semantics

After a successful --release build:

  1. Compute content hash of the binary (per SPEC §8.4).
  2. Determine release tag: <branch>-<short-hash> (first 8 chars).
  3. Package archive per SPEC §6.6.4: <binary>-<platform>.tar.gz (or .zip for windows).
  4. Forgejo API: create release on the source repo with this tag if not exists.
  5. Forgejo API: upload archive as release asset.
  6. Idempotent: if release already exists with same archive, no-op.

Auth: FORGEJO_TOKEN from environment (secrets repo).

hero_claude_rust integration (a command)

The v0 a 2 becomes hero_dev agent --effort 2 (with a aliased). Internally:

hero_dev agent --effort N [--prompt P]
  → resolve Claude credential per §AI API key model
  → connect to ~<user>/hero/var/sockets/hero_claude/rpc.sock
  → hero_claude_rust.agent.create({ effort: N, prompt: P, working_dir: $PWD,
                                    credential: <resolved> })
  → return job_id and tail stream-json

Each user has their own hero_claude_rust instance per their manifest. Sessions don't cross slots.

AI API key model

Two coexisting auth flows on the team box.

Layer 1 — Personal (optional, per user). Each dev runs claude login if they have their own Claude Code-capable account (Claude Max or equivalent). State persists in ~<user>/.config/claude/. Org doesn't manage it. Goes with them when they leave.

Layer 2 — Org pool (org-owned, N keys). The org provisions N Claude Max accounts (e.g. 4 to start). The keys live in hero_aibroker's hero_proc secret store as named slots: ORG_CLAUDE_KEY_1, ORG_CLAUDE_KEY_2, ... Driver account owns them. The org can audit, rotate, or revoke without touching anyone's personal accounts.

Assignment is declarative in hero_team via the optional [claude_org_key] block in users/<name>.toml. A PR is how org keys get assigned or reassigned. Auditable, GitOps, no shell required.

User-level toggle stored in their dev_manifest.toml at [claude] preference:

hero_dev claude use personal   # always use ~/.config/claude/
hero_dev claude use org        # always use the assigned ORG_CLAUDE_KEY_N
hero_dev claude use auto       # default — follow resolution order below
hero_dev claude status         # show which credential is active + key tail (last 4)

Default resolution order when a / hero_dev agent spawns a session:

  1. Explicit user preference if set to personal or org.
  2. Org assignment in hero_team if present (auto resolves here when assignment exists).
  3. Personal ~/.config/claude/ if claude login was done.
  4. Error with clear message: "no Claude credential available — run claude login or ask Mik to assign you an org key."

Org-key access via hero_aibroker — new RPC:

aibroker.claude_key.get({
  user: string,    # caller-asserted; broker re-verifies against hero_team
  slot: string,    # e.g. "ORG_CLAUDE_KEY_1"
}) -> {
  key:        string,    # in-memory only, never persisted in caller's slot
  slot:       string,
  account:    string,    # display name, for UI surfacing
}

Authorization: broker reads the freshest reconciled hero_team's users/<user>.toml and returns the key only if [claude_org_key].enabled = true AND the requested slot matches key_secret_name (or is in key_secret_names). hero_claude_rust calls this on session spawn; the key is never persisted in the user's slot. Security property: an unprivileged user on the box cannot read another user's org key, because the broker enforces assignment and the slot's hero_claude_rust runs as that user's UID.

Multi-key-per-slot (deferred to v1.x). The schema reserves key_secret_names = [...] as a list. v1.x will teach hero_claude_rust to round-robin keys across parent + subagent processes to dodge per-account rate limits. v1 implementation honours only the first element if a list is given; key_secret_name (singular) is sugar for a one-element list and is the canonical form for v1.

Other AI calls (non-Claude-Code) keep going through hero_aibroker directlyhero_agent, hero_embedder, anything reading AIBROKER_API_ENDPOINT. Org-shared OpenAI / OpenRouter / Anthropic-direct keys live in the same broker secret store. Per-user secrets.toml may carry personal keys for non-Claude-Code providers if a dev wants them for their own experiments — never org-shared keys.


Repo-by-repo work breakdown

Each item should become a child issue once this story is approved.

NEW repo: lhumina_code/hero_team

  • Create the repo on forge.
  • Seed users/<founder>.toml for each existing dev (port from current /etc/passwd on the v0 box).
  • Seed manifest_templates/developer.toml and manifest_templates/integration.toml.
  • Seed users/integration.toml.
  • README explaining schema and PR workflow.
  • Sizing: 0.5d.

lhumina_code/hero_codescalers

  • New crate hero_team_sync (or sibling repo per open question 1). Daemon per the contract above.
  • New RPC method build.submit per the contract above. Wires into existing nu_exec dispatch.
  • user.create enhancements: provision ~/hero/cfg/dev_manifest.toml from template; create per-user secrets repo via Forgejo API.
  • Admin UI: new "fleet view" page (grid of user × service with branch/hash/status + claude credential column). Read-only v1.
  • Add service team_sync lifecycle to hero_skills.
  • Sizing: 5d.

lhumina_code/hero_aibroker

  • New RPC method aibroker.claude_key.get per the contract above.
  • Reads freshest reconciled hero_team (cached in memory, refreshed on hero_team_sync events) for authorization.
  • Stores ORG_CLAUDE_KEY_* values in hero_proc secret store; admin RPCs to set / rotate / revoke.
  • Sizing: 2d.

lhumina_code/hero_code (workspace home of hero_builder and hero_dev)

  • New crate hero_dev per the CLI surface above.
  • hero_builder: implement --publish flag per spec (uses Forgejo release API).
  • hero_builder: implement --from release / --from hash modes (download release asset, install to ~/hero/bin/, no-compile path).
  • Sizing: 5d.

lhumina_code/hero_claude_rust

  • Accept a credential parameter on session spawn (env-var or RPC param) so hero_dev can inject the resolved Claude credential.
  • Document the existing claude login flow in the README as Layer-1 fallback.
  • Hooks / MCP / bidirectional control are explicitly v2.
  • Sizing: 1d.

lhumina_code/hero_router

  • Confirm /<user>/<service>/... prefix routing is wired and discovered automatically as users are provisioned. May need a small enhancement to read socket paths from hero_codescalers' user list rather than static config.
  • New endpoint /hooks/forge for inbound forge webhooks (HMAC-validated) → forwards to hero_codescalers.webhook.handle.
  • Sizing: 2d.

lhumina_code/hero_skills

  • Update service_complete to delegate to hero_dev start or be removed (closes hero_skills#106).
  • Update init sync similarly (closes hero_skills#107 if related; assess against #108–112).
  • Migrate nutools/modules/services/service_*.nu modules to delegate to hero_builder per D-08 (currently still uses plain cargo build).
  • Sizing: 3d.

lhumina_code/hero_demo

  • Extend bootstrap_droplet_source.sh to (a) clone hero_team, (b) start hero_team_sync, (c) provision the integration slot first, (d) verify webhooks reach the box, (e) prompt operator to seed ORG_CLAUDE_KEY_* into hero_aibroker.
  • Update docs/ops/DEPLOYMENT.md with the new flow.
  • Sizing: 1.5d.

Total: ~20 dev-days. Two-and-a-bit calendar weeks if focused.


Migration from #121 — command mapping

v0 command (#121) v1 equivalent Notes
init sync hero_dev sync Aliased; same effect
service_complete hero_dev start Fixes hero_skills#106
service_complete --update hero_dev sync && hero_dev start Sync then start
secrets_sync hero_dev secrets sync (Or kept verbatim; secrets out-of-scope for hero_dev itself)
secrets_edit hero_dev secrets edit Same
secrets push hero_dev secrets push Same
claude login (still used; Layer-1 personal credential) Optional per user
a 2 hero_dev agent --effort 2 a aliased; resolves credential per AI key model
Manual user creation PR against lhumina_code/hero_team Mahmoud reviews, doesn't shell
forge worktree (still used; hero_dev switch calls under the hood)

All v0 commands keep working as aliases for at least one quarter post-v1 to avoid breaking muscle memory.


Acceptance criteria

A fresh DO droplet, bootstrapped via bootstrap_droplet_source.sh, with a freshly-seeded lhumina_code/hero_team containing 2-3 user files and ORG_CLAUDE_KEY_1 seeded into hero_aibroker, must satisfy:

  • mosh alice@<box> works after hero_team_sync reconciles; ~alice/hero exists per developer template.
  • hero_dev status shows alice's manifest with the template defaults; services running and reachable at /<alice>/...; claude column shows the resolved credential.
  • hero_dev switch hero_router development_mik --from release swaps to a previously-published artifact without compiling.
  • git commit in alice's worktree + hero_dev rebuild hero_slides rebuilds in <30s.
  • Squash-merge to development of any tracked repo triggers integration slot rebuild AND publishes a Forgejo release archive.
  • Fleet view at /admin shows the correct grid for all slots; integration row green; claude column populated.
  • PR adding users/sara.toml to hero_team (with [claude_org_key].key_secret_name = "ORG_CLAUDE_KEY_2") → after merge, sara's slot exists, mosh-able, with starter manifest, lhumina_code-private/secrets_sara exists on forge, and a 2 works for her without local claude login.
  • User without org assignment AND without claude login gets a clear error from a 2 ("run claude login or ask Mik to assign you an org key").
  • hero_dev claude use personal then a 2 uses Layer-1 (verifiable via hero_dev claude status).
  • PR removing a user → slot deactivated; ~/hero archived to ~/hero/var/team_sync/archive/.
  • Box-loss drill: tear down the droplet, spin a new one, run bootstrap → team back online, users can pull from: release artifacts immediately, org keys restored from operator-provided seed; total elapsed ≤45 minutes.
  • hero_skills#106–112 either resolved or explicitly tracked as out-of-scope-for-v1 with rationale.

Validation plan

After the work breakdown lands on development across the relevant repos:

  1. Spin a fresh DO droplet (~$0.30/run per s84 baseline).
  2. Run bootstrap_droplet_source.sh.
  3. Seed ORG_CLAUDE_KEY_1 and ORG_CLAUDE_KEY_2 into hero_aibroker.
  4. Walk every acceptance-criteria checkbox manually.
  5. Capture screenshots of the fleet view + per-user URLs in the validation report.
  6. Tear down on success; close this issue with the report attached.
  7. Update home#121 noting v1 has shipped and #121 is now historical reference.

Out of scope for v1 (explicit non-goals)

  • Multi-machine coordination. No master-of-record + N builders, no peer forwarding, no cross-host state replication. v2 only when there's a real reason.
  • iroh-docs P2P state replication. Stay on sled (single-host KV) until cross-machine becomes a hard requirement. The hero_codescalers README's iroh-docs claim is aspirational; we do not deliver it here.
  • Multi-key-per-slot for subagent fan-out. Schema reserved (key_secret_names = [...]); implementation v1.x.
  • hero_claude_rust extensions. Hooks, in-process MCP servers, bidirectional control protocol, permission callbacks — separate story. v1 keeps the existing one-shot session model plus credential injection.
  • Per-user spend / rate-limit visibility in fleet view. Future enhancement; the data exists in Anthropic's dashboard and isn't easily exposed via claude CLI today.
  • Production deployment patterns. The team box is for the team; production replicas may pull the same forge artifacts but their lifecycle is a different story.
  • Auto-merge / auto-promotion. Humans squash-merge; the integration slot just builds whatever is on development.
  • GitHub mirroring or off-forge delivery. Forge is the only source of truth.

Open questions / decisions to make during implementation

These are deliberately not locked in this story; PR authors decide as they go.

  1. hero_team_sync home: dedicated crate inside lhumina_code/hero_codescalers/crates/, or sibling repo lhumina_code/hero_team_sync? Recommendation: co-located with hero_codescalers (tightly coupled to its RPCs, shares the hero Unix group prerequisite).
  2. Default release platforms: linux-musl-x86_64 only for v1, or also linux-musl-arm64 from day 1? Recommendation: x86_64 only — matches D-07; arm64 added when first arm64 dev/CI machine appears.
  3. Webhook secret model: shared secret per hero_team repo, or per-source-repo? Recommendation: per-source-repo HMAC for build webhooks; shared secret for the hero_team reconciler webhook.
  4. Mycelium identity per-user vs shared: each slot its own peer ID, or share the box's daemon? Recommendation: share for v1, per-user is v2.
  5. Auto-rebuild on commit default: opt-in per row (auto_rebuild = true) vs always-on. Recommendation: opt-in — predictable; avoids accidental builds during interactive rebases.
  6. Org Claude key seed/restore discipline: how are ORG_CLAUDE_KEY_* seeded into hero_aibroker on box rebuild? Manual operator step (paste the keys back in), or encrypted-at-rest backup? Recommendation: manual operator step for v1 — keys are highest-sensitivity org assets, no automated backup; documented in the runbook.
  7. Per-user Claude org-key allowlist: should [claude_org_key].key_secret_name be free-form, or must match a value the org has explicitly created in hero_aibroker? Recommendation: must matchhero_team_sync validates on PR merge and refuses unknown slot names.

Standing rules in force

  • Never commit directly to development (except hero_demo doc-only). Per-PR squash-merge gate.
  • Branch convention: literal development_<user> (no topic suffix). The branch_suffix in users/<name>.toml matches.
  • Sign all commits as mik-tf.
  • Per D-08, hero_builder is the canonical build tool. v1 leans on this hard.
  • Per D-07, x86_64 source-build is the priority axis; aarch64 archived not deleted.
  • cargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings && cargo build --workspace --release before merging non-trivial PRs.
  • Use absolute URLs for all cross-repo issue/PR references.

Linked issues

Predecessor:

  • home#121 — v0 dev environment runbook (Mahmoud)

Bug list explicitly in v1 acceptance:

Related:

  • home#230 — DO droplet bootstrap (s84-validated; v1 builds on top)
  • hero_demo#52 — Hero OS vision (the team box exists to serve this)

Decisions:

  • D-07 — x86_64 source-build priority
  • D-08 — hero_builder canonical

Signed-off-by: mik-tf

# Hero Team Box — the team's shared dev environment > **Story format. General → particular. Executive summary first; everything an AI would need to code v1 follows.** --- ## Executive Summary A single shared Hero box where every developer gets a Linux user slot with a fully-provisioned `~/hero`, develops on the box itself via mosh + tmux, and binds each service to a specific `(repo, branch)` they care about. Pushes trigger automatic rebuilds in the matching slot(s). Every successful build publishes a content-hashed archive as a Forgejo release on the source repo, so any other slot — or any external machine — can run that exact binary without compiling. Team membership lives in a new GitOps repo (`lhumina_code/hero_team`); per-user secrets stay in each dev's existing private secrets repo. Claude credentials follow a two-layer model: personal `claude login` per user (optional) plus an org pool of N Claude Max keys assigned to users declaratively via `hero_team`. The point: today five people each fight their own laptop. Tomorrow five people share one canonical Hero, see each other's running services through per-user URLs, and the integration slot continuously builds `development` so the team always knows whether the trunk works. This is v1. Single host (DO droplet, validated end-to-end). Multi-machine farms are explicitly v2. --- ## Why this matters — the "5 > 0" framing Today: every dev fights their own machine. Setup drift, version skew, "works on my laptop." Five Heros, zero shared ground. Tomorrow: one box, five user slots, one Hero. SSH in, you have a working environment. See someone else's running service in your browser. Merge to `development`, see it picked up by the integration slot. Lose the box, spin a new one in 30 minutes from a forge-tracked manifest. The shared box converts the team from N parallel single-player setups into one cooperative environment. The build automation is plumbing. The shared environment is the prize. --- ## What this supersedes This story is the v1 elaboration of [home#121 — HeroOS Development Environment Setup](https://forge.ourworld.tf/lhumina_code/home/issues/121), Mahmoud's v0 runbook for the current shared box (manual user creation, `init sync`, `service_complete --update`, `a 2`, manual secrets setup). #121 stays open as the operational runbook until v1 lands; the bugs Sameh filed at hero_skills#106–112 are explicitly part of v1's acceptance. The v1 upgrade: - Manual `useradd` → GitOps via `lhumina_code/hero_team` - `service_complete` (broken per hero_skills#106) → `hero_dev start` (subsumes + fixes) - Implicit per-host shared state → explicit per-slot manifest binding `(service, repo, branch)` - Local-only builds → builds publish content-hashed Forgejo releases automatically - Ad-hoc Claude key handling → two-layer model (personal + org pool) with declarative assignment - "What's everyone running?" answered by Slack → answered by `hero_dev peek` and the fleet view at `/admin` --- ## The complete vision — UX by role ### Joining the team (new hire flow) Mik opens a PR against `lhumina_code/hero_team` adding `users/sara.toml`: ```toml name = "sara" ssh_pubkeys = ["ssh-ed25519 AAAA..."] groups = ["developers", "hero"] branch_suffix = "sara" manifest_template = "developer" [claude_org_key] # optional — present if org assigns her a key enabled = true key_secret_name = "ORG_CLAUDE_KEY_2" ``` After merge, the `hero_team_sync` daemon on the box reconciles: Sara's Linux account is created, SSH keys installed, groups set, `~/hero` is provisioned from the `developer` template (pinning core services to `development from: release`), her private secrets repo `lhumina_code-private/secrets_sara` is created on forge via API. Sara installs mosh on her laptop. Runs `mosh sara@team.hero.box`. First-login script prompts for her Forge token, runs `secrets_sync` to clone her secrets repo, opens `secrets.toml` for her to fill in API keys, runs `secrets push`. Then either: - If she's pre-assigned an org Claude key (per the block above), she sees a message: "You've been assigned `ORG_CLAUDE_KEY_2` — `a 2` is ready to use." - Otherwise the script runs `claude login` so she can use her personal account. - Or both — she does `claude login` AND has an org key; she can toggle later via `hero_dev claude use`. Then `hero_dev status` shows her stack running, with reachable URLs: ``` https://team.hero.box/sara/proc/admin https://team.hero.box/sara/router/admin https://team.hero.box/sara/slides https://team.hero.box/sara/claude ``` Total elapsed: ~10 minutes. She's productive. ### Daily flow (existing dev) Alice mosh-ins, lands in her tmux session (preserved across mosh disconnects). Runs `hero_dev status`: ``` hero_proc → development @ 7c6b5a4 running from-release hero_router → development @ 9f8e7d6 running from-release hero_slides → development_alice @ a1b2c3d running from-build hero_claude → development @ b4c5d6e running from-release [org-key-1] ``` She works on hero_slides in `~/hero/code/hero_slides/worktrees/development_alice/` (worktree created earlier by `hero_dev switch`). Edits, commits. Runs `hero_dev rebuild hero_slides` — hero_builder runs, binary swaps, hero_proc restarts. Refresh `https://team.hero.box/alice/slides`; change is live. When happy, pushes `development_alice` to forge, opens a PR. Once merged to `development`, the integration slot rebuilds. Everyone with `hero_slides @ development from: release` automatically gets the new binary on next reconcile (or `hero_dev pull`). ### Testing someone else's branch ``` hero_dev switch hero_router development_mik --from release ``` Pulls Mik's already-published archive from forge (no compile), swaps Alice's hero_router binary, restarts. To revert: `hero_dev switch hero_router development --from release`. If Alice wants to compile it instead: `--from build`. A worktree is created at `~/hero/code/hero_router/worktrees/development_mik/`, hero_builder compiles, hero_proc restarts. ### Peeking at the team ``` hero_dev peek thabet ``` Shows Thabet's manifest + state. `https://team.hero.box/thabet/slides` shows what he's running. No screen-shares, no "what version" Slack threads. ### Running an agent `a 2` spawns a `hero_claude_rust` session in Alice's slot at effort level 2 (alias for `hero_dev agent --effort 2`). It uses whichever Claude credential the resolution order picks — see §Specifications/AI API key model. Plan mode, Ralph loop, Forgejo-issue auto-clone-and-comment per existing hero_claude_rust surface, scoped to her user. ### Integrator's view (Mik) `https://team.hero.box/admin` — augmented `hero_codescalers_admin`. Grid of users × services. Integration row at the top: green = `development` is shippable. Each cell shows branch, hash, build status, run status, last-rebuild-at. Plus a per-user "claude credential" column showing `org-key-N`, `personal`, or `not configured`. Click integration row → latest forge release tags + archive download links. One screen, full-stack truth. ### Founder / visitor Same dashboard. Sees who's working on what, whether the merged stack works, what's been released, who's using which Claude key. ### When the box dies Spin a new DO droplet, run `bootstrap_droplet_source.sh` (s84-validated per [home#230](https://forge.ourworld.tf/lhumina_code/home/issues/230)), point at `lhumina_code/hero_team`. Reconciler recreates every user account, SSH keys included; each user's `~/hero` provisions from template; secrets repos pull fresh on first login. Org Claude keys are restored from the driver's `hero_aibroker` secret store (separate backup discipline — see Open Questions). Team back online in ~30 minutes. The only thing not preserved is unpushed work in worktrees — exactly what nobody should be holding hostage on a single machine. --- ## Architecture ``` DO droplet (or Hetzner / any commodity x86_64 Ubuntu) host: team.hero.box │ ├── driver account │ ├── hero_router ← single TCP entry point; /:user/:service/... routing │ ├── hero_codescalers ← coordinator; build.submit RPC; user.create/adopt │ ├── hero_aibroker ← org AI key vault (incl. ORG_CLAUDE_KEY_*) │ ├── hero_team_sync (NEW) ← reconciles lhumina_code/hero_team into the box │ └── hero_proc ← driver's own supervisor │ ├── user: alice │ ├── ~/hero/cfg/dev_manifest.toml ← her bindings + claude.preference │ ├── ~/hero/code/<repo>/worktrees/<branch>/ ← per-branch git worktrees │ ├── ~/.config/claude/ ← personal claude login (if done) │ ├── hero_proc, hero_router, hero_slides, hero_claude_rust, ... │ └── reachable at https://team.hero.box/alice/... │ ├── user: thabet ... ├── user: mik ... ├── user: sara ... │ └── user: integration ├── manifest pins everything to development from: build └── reachable at https://team.hero.box/integration/... External (forge.ourworld.tf): ├── lhumina_code/<each repo> ← code + per-build-hash forge releases (artifacts) ├── lhumina_code/hero_team (NEW) ← team membership + manifest templates (GitOps) └── lhumina_code-private/secrets_<user> ← per-user private secrets (one repo each) ``` ### Data flow — push to forge, integration slot rebuilds ``` 1. Mik squash-merges PR to lhumina_code/hero_router development 2. forge fires webhook → team.hero.box (https://team.hero.box/hooks/forge) 3. hero_router (driver) routes to hero_codescalers 4. hero_codescalers reads every slot's manifest: - alice's slot: hero_router → development from: release → MATCH (release-following) - mik's slot: hero_router → development_mik → no match - integration: hero_router → development from: build → MATCH (will rebuild) 5. Dispatches: a. integration slot → nu_exec → cd worktree → hero_builder --release --publish → publishes hero_router-development-<hash>.tar.gz as forge release → hero_proc service restart hero_router b. alice's slot → hero_dev pull hero_router (next reconcile tick) → fetches new release archive → swap binary → hero_proc restart 6. Posts result back as commit comment on the merge commit ``` ### Data flow — push to hero_team ``` 1. Anyone PRs against lhumina_code/hero_team (add user, change template, rotate key) 2. After merge, forge fires webhook → hero_team_sync 3. Reconciles: - new users → hero_codescalers user.create + provision ~/hero from template + Forgejo API: create lhumina_code-private/secrets_<user> - updated SSH keys → rewrite ~<user>/.ssh/authorized_keys - updated [claude_org_key] → no-op on the box; the assignment is consulted live by hero_aibroker on each session spawn - removed users → deactivate slot (chage -E 0); snapshot ~/hero to archive dir - template changed → seed for FUTURE users only; existing manifests untouched 4. Updates last_synced_commit; posts commit-comment with reconcile result. ``` ### Data flow — Claude session spawn ``` 1. Alice runs `a 2` (alias for hero_dev agent --effort 2) 2. hero_dev resolves the credential per the order (preference > org > personal > error): - reads alice's dev_manifest.toml [claude.preference] - if set to "org" or "auto", calls aibroker.claude_key.get({ user: alice, slot: ... }) - hero_aibroker authorizes by re-reading hero_team's users/alice.toml - returns key in-memory only (never persisted in alice's slot) 3. hero_claude_rust spawns the `claude` CLI subprocess with that credential 4. stream-json events flow back; alice sees output live ``` ### Data flow — local rebuild in a slot ``` 1. Alice edits in ~/hero/code/hero_slides/worktrees/development_alice/ 2. Commits in that worktree 3. (a) auto_rebuild = false (default): nothing until she runs hero_dev rebuild (b) auto_rebuild = true: post-commit hook triggers hero_dev rebuild 4. hero_dev calls hero_codescalers.build.submit { user: alice, repo: hero_slides, branch: development_alice, from: build } 5. → hero_builder compiles in the worktree → installs to ~alice/hero/bin/ → hero_proc restart hero_slides 6. (Optional) push to forge → integration slot reaction per the first flow ``` --- ## The binary / release layer This is the structural piece that makes "5 > 0" durable. Every `hero_builder` run does **two things**: 1. Installs the binary into that user's `~/hero/bin/` for their running services. 2. With `--publish`: packages a content-hashed archive and publishes it as a Forgejo release on the source repo. Tag format: `<branch>-<short-hash>` (e.g. `development_mik-a1b2c3d`). Archive: `<binary>-<platform>.tar.gz` (e.g. `hero_router-linux-musl-x86_64.tar.gz`). This single mechanism delivers: - **No-compile collaboration**: pull, don't build (`--from release`). - **Automatic CI artifacts**: every merge to `development` produces a release. - **Reproducibility**: hash-pinned manifests; same binary everywhere. - **musl static binaries via `cross`**: per [hero_builder SPEC §6.5/6.6](https://forge.ourworld.tf/lhumina_code/hero_code/src/branch/development/crates/hero_builder/SPEC.md). One binary on any glibc/musl Linux of the matching arch. - **Multi-platform from one box**: same release tag carries `linux-musl-x86_64`, `linux-musl-arm64`, `macos-arm64`, etc. **The build IS the release. No separate CI/CD.** --- ## Specifications ### Dev manifest — `~/hero/cfg/dev_manifest.toml` Source of truth for what a single slot is running. Owned by the user (the `developer` template seeds it on `user.create`; never overwritten by reconciler). ```toml [[bind]] service = "hero_slides" repo = "lhumina_code/hero_slides" branch = "development_alice" from = "build" # build | release | hash auto_rebuild = false # auto-rebuild on commit in worktree (opt-in) [[bind]] service = "hero_router" repo = "lhumina_code/hero_router" branch = "development" from = "release" # pull pre-built artifact (latest matching <branch>-*) [[bind]] service = "hero_proc" repo = "lhumina_code/hero_proc" branch = "development" from = "hash" hash = "a1b2c3d4" # pin to exact release archive # User-level Claude credential preference (see §AI API key model) [claude] preference = "auto" # auto | personal | org # Per-slot state (managed by hero_dev; user shouldn't edit by hand) [state.hero_slides] last_build_hash = "a1b2c3d" last_build_status = "ok" last_build_at = "2026-05-09T14:30:12Z" last_run_status = "running" ``` Worktree convention: each binding implies `~/hero/code/<repo-basename>/worktrees/<branch>/`. The reconciler ensures the worktree exists; `hero_dev` operates inside it. ### `lhumina_code/hero_team` repo schema ``` hero_team/ ├── README.md (schema, PR workflow, reconcile semantics) ├── users/ │ ├── alice.toml │ ├── mik.toml │ ├── thabet.toml │ ├── timur.toml │ └── integration.toml └── manifest_templates/ ├── developer.toml (services pinned to development from: release) └── integration.toml (services pinned to development from: build) ``` `users/<name>.toml`: ```toml name = "alice" # MUST match Linux username ssh_pubkeys = ["ssh-ed25519 AAAA..."] groups = ["developers", "hero"] # "hero" required for inter-user socket access branch_suffix = "alice" # convention: dev branches are development_<suffix> manifest_template = "developer" # which manifest_templates/<name>.toml seeds the slot display_name = "Alice Smith" # optional, for fleet view email = "alice@..." # optional deactivated = false # true to deactivate without deleting # Optional: org-pool Claude key assignment (see §AI API key model) [claude_org_key] enabled = true key_secret_name = "ORG_CLAUDE_KEY_1" # singular form (sugar) # OR for multi-key (v1.x): # key_secret_names = ["ORG_CLAUDE_KEY_1", "ORG_CLAUDE_KEY_2"] ``` `manifest_templates/<role>.toml` is a `[[bind]]` list, same schema as `dev_manifest.toml` minus `[state.*]` blocks. ### `hero_dev` CLI surface ``` hero_dev status # show all bindings + state + claude credential hero_dev sync # alias for old `init sync` — pull repo updates hero_dev start # start all services per manifest hero_dev stop # stop all services hero_dev restart [<service>] # restart all or one hero_dev switch <service> <branch> [--from build|release|hash] # rebind; ensure worktree; (build or pull); restart hero_dev bind <service> <repo> <branch> [--from ...] # add a NEW row to manifest hero_dev unbind <service> # remove a row hero_dev rebuild [<service>] # force rebuild + restart (from: build only) hero_dev pull [<service>] # for from:release rows, fetch latest matching archive hero_dev peek <user> # show another user's manifest + state hero_dev fork <repo> # create development_<my-suffix> branch on forge, # bind locally, switch to from: build hero_dev logs <service> [-f] # tail hero_proc logs for a service hero_dev agent [--effort N] [--prompt ...] # spawn hero_claude_rust session; alias: `a N` hero_dev claude use {personal|org|auto} # set claude credential preference for this slot hero_dev claude status # show which credential is active + key tail hero_dev doctor # diagnose: socket reachability, hero_proc liveness, # forge token validity, secrets-repo presence, # claude credential reachable, hero_aibroker reachable ``` Implementation notes: - Calls `hero_codescalers.build.submit` for cross-slot operations; `hero_proc` directly for local restart. - `--from release` resolves to latest release matching `<branch>-*` (lexical sort by hash); `--from hash <h>` pins exactly. - `hero_dev fork` requires `FORGEJO_TOKEN` (read from secrets repo). - Cross-user fan-out always through `hero_codescalers`, never direct. - `a` is a shell alias forwarding to `hero_dev agent`. ### `hero_codescalers.build.submit` RPC ``` build.submit({ user: string, # target slot's Linux username repo: string, # e.g. "lhumina_code/hero_router" branch: string, from: "build" | "release" | "hash", hash: string?, # required if from="hash" publish: bool = false, # true → push archive to forge release on success restart: bool = true, # true → hero_proc restart after build/pull }) -> { ok: bool, build_hash: string, duration_ms: u64, log: string, # truncated; full log in hero_codescalers job record job_id: string, } ``` Implementation: dispatches a `nu_exec` job into the target user's hero_proc with tag `build-<repo>-<branch>`. Job runs `hero_dev sync` then `hero_builder --release [--publish]` in the worktree, then `hero_proc service restart <name>` if requested. ### `hero_team_sync` daemon contract ``` Lifecycle: long-running daemon under driver account, supervised by driver's hero_proc. Inputs: - HTTP webhook endpoint at /team-sync/webhook (forge POSTs here on hero_team push) - 5-minute periodic poll fallback - hero_codescalers RPC client - Forgejo API client (secrets-repo creation) State: - last_synced_commit (in driver's ~/hero/var/team_sync/state.toml) - reconcile log (append-only at ~/hero/var/team_sync/reconcile.log) Reconcile rules: 1. Fetch hero_team @ development. 2. Diff against last_synced_commit. 3. New users/<name>.toml: - hero_codescalers.user.create({name, ssh_pubkeys, groups}) - Provision ~/hero from manifest_templates/<template>.toml - Forgejo API: create lhumina_code-private/secrets_<name> if not exists - Add starter secrets.toml.example 4. Modified users/<name>.toml: - ssh_pubkeys changed → rewrite ~<name>/.ssh/authorized_keys - groups changed → usermod -G ... - manifest_template changed → WARN ONLY (do not clobber existing manifest) - [claude_org_key] changed → no box-side action; consulted live by hero_aibroker - deactivated=true → disable login (chage -E 0); do not delete 5. Removed users/<name>.toml: - Same as deactivated=true - Snapshot ~<name>/hero to ~/hero/var/team_sync/archive/<name>-<timestamp>.tar.gz 6. Update last_synced_commit; commit-comment on merge with reconcile result. Idempotence: every operation idempotent; re-run with same state is a no-op. Failure: per-user errors logged but don't block other users; top-level errors leave last_synced_commit unchanged so next tick retries. ``` ### URL routing convention `hero_router` routes `https://<host>/<user>/<service>/...` to `~<user>/hero/var/sockets/hero_<service>/web.sock` (or `ui.sock` per service convention). Already supported via the `hero_web_prefix` skill; this story makes it the **default** for every user-provisioned service. The `<service>` prefix in the URL is the short name (e.g. `slides`, not `hero_slides`). ### Worktree layout convention ``` ~/hero/code/<repo-basename>/ ← bare or main checkout ~/hero/code/<repo-basename>/worktrees/<branch>/ ← one worktree per active branch ``` `hero_dev switch <service> <branch>` ensures the worktree exists, then operates inside it. Branch-switching never destroys uncommitted work because each branch gets its own directory. `forge worktree` (per home#121) is the underlying mechanism; `hero_dev` is the higher-level UX on top. ### `hero_builder --publish` semantics After a successful `--release` build: 1. Compute content hash of the binary (per [SPEC §8.4](https://forge.ourworld.tf/lhumina_code/hero_code/src/branch/development/crates/hero_builder/SPEC.md)). 2. Determine release tag: `<branch>-<short-hash>` (first 8 chars). 3. Package archive per [SPEC §6.6.4](https://forge.ourworld.tf/lhumina_code/hero_code/src/branch/development/crates/hero_builder/SPEC.md): `<binary>-<platform>.tar.gz` (or `.zip` for windows). 4. Forgejo API: create release on the source repo with this tag if not exists. 5. Forgejo API: upload archive as release asset. 6. Idempotent: if release already exists with same archive, no-op. Auth: `FORGEJO_TOKEN` from environment (secrets repo). ### hero_claude_rust integration (`a` command) The v0 `a 2` becomes `hero_dev agent --effort 2` (with `a` aliased). Internally: ``` hero_dev agent --effort N [--prompt P] → resolve Claude credential per §AI API key model → connect to ~<user>/hero/var/sockets/hero_claude/rpc.sock → hero_claude_rust.agent.create({ effort: N, prompt: P, working_dir: $PWD, credential: <resolved> }) → return job_id and tail stream-json ``` Each user has their own hero_claude_rust instance per their manifest. Sessions don't cross slots. ### AI API key model Two coexisting auth flows on the team box. **Layer 1 — Personal (optional, per user).** Each dev runs `claude login` if they have their own Claude Code-capable account (Claude Max or equivalent). State persists in `~<user>/.config/claude/`. Org doesn't manage it. Goes with them when they leave. **Layer 2 — Org pool (org-owned, N keys).** The org provisions N Claude Max accounts (e.g. 4 to start). The keys live in **`hero_aibroker`'s hero_proc secret store** as named slots: `ORG_CLAUDE_KEY_1`, `ORG_CLAUDE_KEY_2`, ... Driver account owns them. The org can audit, rotate, or revoke without touching anyone's personal accounts. **Assignment is declarative in `hero_team`** via the optional `[claude_org_key]` block in `users/<name>.toml`. A PR is how org keys get assigned or reassigned. Auditable, GitOps, no shell required. **User-level toggle** stored in their `dev_manifest.toml` at `[claude] preference`: ``` hero_dev claude use personal # always use ~/.config/claude/ hero_dev claude use org # always use the assigned ORG_CLAUDE_KEY_N hero_dev claude use auto # default — follow resolution order below hero_dev claude status # show which credential is active + key tail (last 4) ``` **Default resolution order** when `a` / `hero_dev agent` spawns a session: 1. Explicit user preference if set to `personal` or `org`. 2. Org assignment in `hero_team` if present (`auto` resolves here when assignment exists). 3. Personal `~/.config/claude/` if `claude login` was done. 4. Error with clear message: "no Claude credential available — run `claude login` or ask Mik to assign you an org key." **Org-key access via `hero_aibroker`** — new RPC: ``` aibroker.claude_key.get({ user: string, # caller-asserted; broker re-verifies against hero_team slot: string, # e.g. "ORG_CLAUDE_KEY_1" }) -> { key: string, # in-memory only, never persisted in caller's slot slot: string, account: string, # display name, for UI surfacing } ``` Authorization: broker reads the freshest reconciled `hero_team`'s `users/<user>.toml` and returns the key only if `[claude_org_key].enabled = true` AND the requested `slot` matches `key_secret_name` (or is in `key_secret_names`). hero_claude_rust calls this on session spawn; the key is never persisted in the user's slot. Security property: an unprivileged user on the box cannot read another user's org key, because the broker enforces assignment and the slot's hero_claude_rust runs as that user's UID. **Multi-key-per-slot (deferred to v1.x).** The schema reserves `key_secret_names = [...]` as a list. v1.x will teach hero_claude_rust to round-robin keys across parent + subagent processes to dodge per-account rate limits. v1 implementation honours only the first element if a list is given; `key_secret_name` (singular) is sugar for a one-element list and is the canonical form for v1. **Other AI calls (non-Claude-Code) keep going through `hero_aibroker` directly** — `hero_agent`, `hero_embedder`, anything reading `AIBROKER_API_ENDPOINT`. Org-shared OpenAI / OpenRouter / Anthropic-direct keys live in the same broker secret store. Per-user `secrets.toml` may carry **personal** keys for non-Claude-Code providers if a dev wants them for their own experiments — never org-shared keys. --- ## Repo-by-repo work breakdown Each item should become a child issue once this story is approved. ### NEW repo: `lhumina_code/hero_team` - Create the repo on forge. - Seed `users/<founder>.toml` for each existing dev (port from current `/etc/passwd` on the v0 box). - Seed `manifest_templates/developer.toml` and `manifest_templates/integration.toml`. - Seed `users/integration.toml`. - README explaining schema and PR workflow. - **Sizing: 0.5d.** ### `lhumina_code/hero_codescalers` - New crate `hero_team_sync` (or sibling repo per open question 1). Daemon per the contract above. - New RPC method `build.submit` per the contract above. Wires into existing `nu_exec` dispatch. - `user.create` enhancements: provision `~/hero/cfg/dev_manifest.toml` from template; create per-user secrets repo via Forgejo API. - Admin UI: new "fleet view" page (grid of user × service with branch/hash/status + claude credential column). Read-only v1. - Add `service team_sync` lifecycle to `hero_skills`. - **Sizing: 5d.** ### `lhumina_code/hero_aibroker` - New RPC method `aibroker.claude_key.get` per the contract above. - Reads freshest reconciled `hero_team` (cached in memory, refreshed on `hero_team_sync` events) for authorization. - Stores `ORG_CLAUDE_KEY_*` values in hero_proc secret store; admin RPCs to `set` / `rotate` / `revoke`. - **Sizing: 2d.** ### `lhumina_code/hero_code` (workspace home of `hero_builder` and `hero_dev`) - New crate `hero_dev` per the CLI surface above. - `hero_builder`: implement `--publish` flag per spec (uses Forgejo release API). - `hero_builder`: implement `--from release` / `--from hash` modes (download release asset, install to `~/hero/bin/`, no-compile path). - **Sizing: 5d.** ### `lhumina_code/hero_claude_rust` - Accept a credential parameter on session spawn (env-var or RPC param) so `hero_dev` can inject the resolved Claude credential. - Document the existing `claude login` flow in the README as Layer-1 fallback. - Hooks / MCP / bidirectional control are explicitly v2. - **Sizing: 1d.** ### `lhumina_code/hero_router` - Confirm `/<user>/<service>/...` prefix routing is wired and discovered automatically as users are provisioned. May need a small enhancement to read socket paths from hero_codescalers' user list rather than static config. - New endpoint `/hooks/forge` for inbound forge webhooks (HMAC-validated) → forwards to `hero_codescalers.webhook.handle`. - **Sizing: 2d.** ### `lhumina_code/hero_skills` - Update `service_complete` to delegate to `hero_dev start` or be removed (closes hero_skills#106). - Update `init sync` similarly (closes hero_skills#107 if related; assess against #108–112). - Migrate `nutools/modules/services/service_*.nu` modules to delegate to `hero_builder` per [D-08](https://forge.ourworld.tf/lhumina_code/home/decisions/D-08-hero_builder-as-canonical-build-tool.md) (currently still uses plain `cargo build`). - **Sizing: 3d.** ### `lhumina_code/hero_demo` - Extend `bootstrap_droplet_source.sh` to (a) clone `hero_team`, (b) start `hero_team_sync`, (c) provision the integration slot first, (d) verify webhooks reach the box, (e) prompt operator to seed `ORG_CLAUDE_KEY_*` into hero_aibroker. - Update `docs/ops/DEPLOYMENT.md` with the new flow. - **Sizing: 1.5d.** **Total: ~20 dev-days.** Two-and-a-bit calendar weeks if focused. --- ## Migration from #121 — command mapping | v0 command (#121) | v1 equivalent | Notes | |----------------------|--------------------------------|--------------------------------------| | `init sync` | `hero_dev sync` | Aliased; same effect | | `service_complete` | `hero_dev start` | Fixes hero_skills#106 | | `service_complete --update` | `hero_dev sync && hero_dev start` | Sync then start | | `secrets_sync` | `hero_dev secrets sync` | (Or kept verbatim; secrets out-of-scope for hero_dev itself) | | `secrets_edit` | `hero_dev secrets edit` | Same | | `secrets push` | `hero_dev secrets push` | Same | | `claude login` | (still used; Layer-1 personal credential) | Optional per user | | `a 2` | `hero_dev agent --effort 2` | `a` aliased; resolves credential per AI key model | | Manual user creation | PR against `lhumina_code/hero_team` | Mahmoud reviews, doesn't shell | | `forge worktree` | (still used; `hero_dev switch` calls under the hood) | | All v0 commands keep working as aliases for at least one quarter post-v1 to avoid breaking muscle memory. --- ## Acceptance criteria A fresh DO droplet, bootstrapped via `bootstrap_droplet_source.sh`, with a freshly-seeded `lhumina_code/hero_team` containing 2-3 user files and `ORG_CLAUDE_KEY_1` seeded into `hero_aibroker`, must satisfy: - [ ] `mosh alice@<box>` works after `hero_team_sync` reconciles; `~alice/hero` exists per `developer` template. - [ ] `hero_dev status` shows alice's manifest with the template defaults; services running and reachable at `/<alice>/...`; claude column shows the resolved credential. - [ ] `hero_dev switch hero_router development_mik --from release` swaps to a previously-published artifact without compiling. - [ ] `git commit` in alice's worktree + `hero_dev rebuild hero_slides` rebuilds in <30s. - [ ] Squash-merge to `development` of any tracked repo triggers integration slot rebuild AND publishes a Forgejo release archive. - [ ] Fleet view at `/admin` shows the correct grid for all slots; integration row green; claude column populated. - [ ] PR adding `users/sara.toml` to `hero_team` (with `[claude_org_key].key_secret_name = "ORG_CLAUDE_KEY_2"`) → after merge, sara's slot exists, mosh-able, with starter manifest, `lhumina_code-private/secrets_sara` exists on forge, and `a 2` works for her without local `claude login`. - [ ] User without org assignment AND without `claude login` gets a clear error from `a 2` ("run `claude login` or ask Mik to assign you an org key"). - [ ] `hero_dev claude use personal` then `a 2` uses Layer-1 (verifiable via `hero_dev claude status`). - [ ] PR removing a user → slot deactivated; `~/hero` archived to `~/hero/var/team_sync/archive/`. - [ ] Box-loss drill: tear down the droplet, spin a new one, run bootstrap → team back online, users can pull `from: release` artifacts immediately, org keys restored from operator-provided seed; total elapsed ≤45 minutes. - [ ] hero_skills#106–112 either resolved or explicitly tracked as out-of-scope-for-v1 with rationale. --- ## Validation plan After the work breakdown lands on `development` across the relevant repos: 1. Spin a fresh DO droplet (~$0.30/run per s84 baseline). 2. Run `bootstrap_droplet_source.sh`. 3. Seed `ORG_CLAUDE_KEY_1` and `ORG_CLAUDE_KEY_2` into hero_aibroker. 4. Walk every acceptance-criteria checkbox manually. 5. Capture screenshots of the fleet view + per-user URLs in the validation report. 6. Tear down on success; close this issue with the report attached. 7. Update [home#121](https://forge.ourworld.tf/lhumina_code/home/issues/121) noting v1 has shipped and #121 is now historical reference. --- ## Out of scope for v1 (explicit non-goals) - **Multi-machine coordination.** No master-of-record + N builders, no peer forwarding, no cross-host state replication. v2 only when there's a real reason. - **`iroh-docs` P2P state replication.** Stay on `sled` (single-host KV) until cross-machine becomes a hard requirement. The hero_codescalers README's iroh-docs claim is aspirational; we do not deliver it here. - **Multi-key-per-slot for subagent fan-out.** Schema reserved (`key_secret_names = [...]`); implementation v1.x. - **`hero_claude_rust` extensions.** Hooks, in-process MCP servers, bidirectional control protocol, permission callbacks — separate story. v1 keeps the existing one-shot session model plus credential injection. - **Per-user spend / rate-limit visibility in fleet view.** Future enhancement; the data exists in Anthropic's dashboard and isn't easily exposed via `claude` CLI today. - **Production deployment patterns.** The team box is for the team; production replicas may pull the same forge artifacts but their lifecycle is a different story. - **Auto-merge / auto-promotion.** Humans squash-merge; the integration slot just builds whatever is on `development`. - **GitHub mirroring or off-forge delivery.** Forge is the only source of truth. --- ## Open questions / decisions to make during implementation These are deliberately not locked in this story; PR authors decide as they go. 1. **`hero_team_sync` home**: dedicated crate inside `lhumina_code/hero_codescalers/crates/`, or sibling repo `lhumina_code/hero_team_sync`? Recommendation: **co-located** with hero_codescalers (tightly coupled to its RPCs, shares the `hero` Unix group prerequisite). 2. **Default release platforms**: `linux-musl-x86_64` only for v1, or also `linux-musl-arm64` from day 1? Recommendation: **x86_64 only** — matches D-07; arm64 added when first arm64 dev/CI machine appears. 3. **Webhook secret model**: shared secret per `hero_team` repo, or per-source-repo? Recommendation: **per-source-repo HMAC** for build webhooks; shared secret for the `hero_team` reconciler webhook. 4. **Mycelium identity per-user vs shared**: each slot its own peer ID, or share the box's daemon? Recommendation: **share** for v1, per-user is v2. 5. **Auto-rebuild on commit default**: opt-in per row (`auto_rebuild = true`) vs always-on. Recommendation: **opt-in** — predictable; avoids accidental builds during interactive rebases. 6. **Org Claude key seed/restore discipline**: how are `ORG_CLAUDE_KEY_*` seeded into `hero_aibroker` on box rebuild? Manual operator step (paste the keys back in), or encrypted-at-rest backup? Recommendation: **manual operator step for v1** — keys are highest-sensitivity org assets, no automated backup; documented in the runbook. 7. **Per-user Claude org-key allowlist**: should `[claude_org_key].key_secret_name` be free-form, or must match a value the org has explicitly created in `hero_aibroker`? Recommendation: **must match** — `hero_team_sync` validates on PR merge and refuses unknown slot names. --- ## Standing rules in force - Never commit directly to `development` (except `hero_demo` doc-only). Per-PR squash-merge gate. - Branch convention: literal `development_<user>` (no topic suffix). The `branch_suffix` in `users/<name>.toml` matches. - Sign all commits as `mik-tf`. - Per [D-08](https://forge.ourworld.tf/lhumina_code/home/decisions/D-08-hero_builder-as-canonical-build-tool.md), `hero_builder` is the canonical build tool. v1 leans on this hard. - Per [D-07](https://forge.ourworld.tf/lhumina_code/home/decisions/D-07-x86-source-build-priority.md), x86_64 source-build is the priority axis; aarch64 archived not deleted. - `cargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings && cargo build --workspace --release` before merging non-trivial PRs. - Use absolute URLs for all cross-repo issue/PR references. --- ## Linked issues Predecessor: - [home#121](https://forge.ourworld.tf/lhumina_code/home/issues/121) — v0 dev environment runbook (Mahmoud) Bug list explicitly in v1 acceptance: - [hero_skills#106](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/106), [#107](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/107), [#108](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/108), [#109](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/109), [#110](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/110), [#111](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/111), [#112](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/112) Related: - [home#230](https://forge.ourworld.tf/lhumina_code/home/issues/230) — DO droplet bootstrap (s84-validated; v1 builds on top) - [hero_demo#52](https://forge.ourworld.tf/lhumina_code/hero_demo/issues/52) — Hero OS vision (the team box exists to serve this) Decisions: - [D-07](https://forge.ourworld.tf/lhumina_code/home/decisions/D-07-x86-source-build-priority.md) — x86_64 source-build priority - [D-08](https://forge.ourworld.tf/lhumina_code/home/decisions/D-08-hero_builder-as-canonical-build-tool.md) — hero_builder canonical --- Signed-off-by: mik-tf
Author
Owner

Execution plan — 4 devs, one working week

Assuming this gets green-lit, ships in ~5 working days plus 1 buffer day. Each dev owns a vertical (one or two related crates, end-to-end), not a horizontal slice.

Four ownership lanes

Lane 1 — Foundation + integration + validation. lhumina_code/hero_team (new repo seed), hero_team_sync daemon, bootstrap_droplet_source.sh extension, end-to-end integration debugging, DO droplet validation. Front-loads the work everyone else depends on; tail-loads into the integration role.

Lane 2 — Build pipeline. hero_builder --publish and --from release|hash, hero_codescalers.build.submit RPC, hero_router /hooks/forge + manifest-aware webhook fan-out. The "publish → pull → run" path needs to compose seamlessly; one owner across the whole pipeline.

Lane 3 — hero_dev CLI. The hero_dev crate end-to-end: every verb (status / switch / bind / unbind / rebuild / pull / peek / fork / logs / agent / claude use|status / doctor), every error message, the first-login script, v0-command aliases. The surface every dev touches every day — one owner, one voice.

Lane 4 — AI keys + UI polish. hero_aibroker.claude_key.get (plus admin set/rotate/revoke), hero_claude_rust credential injection, fleet view in hero_codescalers_admin, hero_skills migration that closes hero_skills#106#112. Lighter raw load, broader surface.

The week

Day Lane 1 Lane 2 Lane 3 Lane 4
0 Kickoff — all four, ~2h
1 Foundation ships Skeleton + prep Skeleton + prep Skeleton + prep
2 Standby / review Build pipeline hero_dev MVP AI keys
3 Standby / review Build pipeline hero_dev verbs AI keys + fleet view
3 PM Mid-week sync — all four, ~1h
4 Bootstrap script Webhook fan-out hero_dev polish hero_skills sweep
5 DO validation Integration support Integration support Integration support
6 Fix-forward / close (as needed) (as needed) (as needed)

Day 1 exit gate: PR adding users/test_user.toml produces a Linux account with ~/hero provisioned. Day 4 exit gates per lane: Lane 2 — hero_dev switch X Y --from release swaps a binary without compiling. Lane 3 — every verb works on a single-user slot. Lane 4 — a user with [claude_org_key] runs a 2 without local claude login. Day 5: fresh DO droplet, acceptance criteria walked top to bottom. Day 6: buffer for fix-forward — home#230/s84 precedent says first DO from-nothing run typically surfaces 2–3 unrelated bootstrap bugs.

Two forced coordination touchpoints

Day 0 kickoff (~2h). Lock the seven open questions in the issue body. Walk the manifest schema, users/<name>.toml, and the three RPC contracts line by line. Catches "wait, what does build.submit return?" before it costs a day.

End of Day 3 sync (~1h). Each lane reports against its exit gate. Specifically verify cross-lane shapes: aibroker.claude_key.get matches hero_dev's call site; build.submit response shape matches webhook fan-out consumer.

Beyond those two, the spec above is the contract — devs read the issue, not each other.

Watchpoints

  • The claude CLI auth surface is opaque from outside; Lane 4's credential injection should be the first thing that lane verifies, not the last — unknown unknowns can cost half a day there.
  • hero_dev fork (Lane 3) needs Forgejo write access to create development_<user> branches programmatically; token-scope rules should be confirmed before designing the verb.
  • Webhook HMAC + nginx + Let's Encrypt + htpasswd layering (s74 setup) deserves a 30-min pre-check Day 1.
  • Pre-existing skills (forge_release, hero_web_prefix, hero_proc_secrets_and_meta) are mature — cite them, don't reinvent.

Bottom line

End of day 5 ships, day 6 buffers, week 2 opens with this issue closed. Biggest leverage on the timeline is whether the Day 0 kickoff is sharp — if everyone leaves it with the same picture in their head, the rest is execution.

Signed-off-by: mik-tf

## Execution plan — 4 devs, one working week Assuming this gets green-lit, ships in ~5 working days plus 1 buffer day. Each dev owns a vertical (one or two related crates, end-to-end), not a horizontal slice. ### Four ownership lanes **Lane 1 — Foundation + integration + validation.** `lhumina_code/hero_team` (new repo seed), `hero_team_sync` daemon, `bootstrap_droplet_source.sh` extension, end-to-end integration debugging, DO droplet validation. Front-loads the work everyone else depends on; tail-loads into the integration role. **Lane 2 — Build pipeline.** `hero_builder --publish` and `--from release|hash`, `hero_codescalers.build.submit` RPC, `hero_router /hooks/forge` + manifest-aware webhook fan-out. The "publish → pull → run" path needs to compose seamlessly; one owner across the whole pipeline. **Lane 3 — `hero_dev` CLI.** The `hero_dev` crate end-to-end: every verb (status / switch / bind / unbind / rebuild / pull / peek / fork / logs / agent / claude use|status / doctor), every error message, the first-login script, v0-command aliases. The surface every dev touches every day — one owner, one voice. **Lane 4 — AI keys + UI polish.** `hero_aibroker.claude_key.get` (plus admin set/rotate/revoke), `hero_claude_rust` credential injection, fleet view in `hero_codescalers_admin`, `hero_skills` migration that closes [hero_skills#106](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/106)–[#112](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/112). Lighter raw load, broader surface. ### The week | Day | Lane 1 | Lane 2 | Lane 3 | Lane 4 | |---|---|---|---|---| | 0 | Kickoff — all four, ~2h | | | | | 1 | Foundation ships | Skeleton + prep | Skeleton + prep | Skeleton + prep | | 2 | Standby / review | Build pipeline | `hero_dev` MVP | AI keys | | 3 | Standby / review | Build pipeline | `hero_dev` verbs | AI keys + fleet view | | 3 PM | Mid-week sync — all four, ~1h | | | | | 4 | Bootstrap script | Webhook fan-out | `hero_dev` polish | hero_skills sweep | | 5 | DO validation | Integration support | Integration support | Integration support | | 6 | Fix-forward / close | (as needed) | (as needed) | (as needed) | **Day 1 exit gate:** PR adding `users/test_user.toml` produces a Linux account with `~/hero` provisioned. **Day 4 exit gates per lane:** Lane 2 — `hero_dev switch X Y --from release` swaps a binary without compiling. Lane 3 — every verb works on a single-user slot. Lane 4 — a user with `[claude_org_key]` runs `a 2` without local `claude login`. **Day 5:** fresh DO droplet, acceptance criteria walked top to bottom. **Day 6:** buffer for fix-forward — [home#230](https://forge.ourworld.tf/lhumina_code/home/issues/230)/s84 precedent says first DO from-nothing run typically surfaces 2–3 unrelated bootstrap bugs. ### Two forced coordination touchpoints **Day 0 kickoff (~2h).** Lock the seven open questions in the issue body. Walk the manifest schema, `users/<name>.toml`, and the three RPC contracts line by line. Catches "wait, what does `build.submit` return?" before it costs a day. **End of Day 3 sync (~1h).** Each lane reports against its exit gate. Specifically verify cross-lane shapes: `aibroker.claude_key.get` matches `hero_dev`'s call site; `build.submit` response shape matches webhook fan-out consumer. Beyond those two, the spec above is the contract — devs read the issue, not each other. ### Watchpoints - The `claude` CLI auth surface is opaque from outside; Lane 4's credential injection should be the *first* thing that lane verifies, not the last — unknown unknowns can cost half a day there. - `hero_dev fork` (Lane 3) needs Forgejo write access to create `development_<user>` branches programmatically; token-scope rules should be confirmed before designing the verb. - Webhook HMAC + nginx + Let's Encrypt + htpasswd layering (s74 setup) deserves a 30-min pre-check Day 1. - Pre-existing skills (`forge_release`, `hero_web_prefix`, `hero_proc_secrets_and_meta`) are mature — cite them, don't reinvent. ### Bottom line End of day 5 ships, day 6 buffers, week 2 opens with this issue closed. Biggest leverage on the timeline is whether the Day 0 kickoff is sharp — if everyone leaves it with the same picture in their head, the rest is execution. Signed-off-by: mik-tf
Owner

needs to be redone

needs to be redone
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/home#232
No description provided.