0.0
The project is in a healthy, maintained state
A dry-cli CLI for spaces: date-prefixed directories with a YAML identity file, $PWD-based current-space resolution, and XDG config/state. Provisions repos at copy-on-write speed from evergreen checkouts, concurrently on fibers. Ships fish shell integration and completions.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 6.0
~> 13.0

Runtime

~> 2.39
~> 1.4
~> 1.10
~> 1.16
~> 1.9
~> 0.8
~> 10.2
~> 0.95
 Project Readme

Space Architect πŸš€

Gem Version

Task-scoped workspaces that double as Architect Loop projects β€” for humans and their agents! βœ¨πŸ›°οΈ

space-architect is a Ruby toolkit for spaces β€” task-scoped project workspaces that hold repos, notes, and artifacts under one obvious filesystem root β€” fused with the Architect Loop: a structured judgment-and-build cycle for you and a fleet of headless AI builders. 🌌

The gem ships three composable binaries over clean library seams:

Binary What it does Library
space πŸͺ Create, manage & containerize task-scoped workspaces Space::Core
architect πŸ—οΈ Run the Architect Loop β€” judgment + headless builders Space::Architect
src 🌲 Tend evergreen repo checkouts for copy-on-write provisioning Space::Src

Each is a first-class executable. architect also forwards architect space … and architect src … to the other two, so a project can drive everything from one command when that's handier. πŸŽ€

What's a space? πŸͺ

A space is just a regular directory with a tiny YAML identity file and room for everything a task needs:

~/architect/spaces/20260531-name-of-space/
  space.yaml        # identity: id, title, status, repos, notes, tags
  README.md
  repos/            # cloned (or copy-on-write'd) repositories
  notes/            # scratch, prompts, logs
  architecture/     # iteration files: I<NN>-<name>.md + ARCHITECT.md index + BRIEF.md
  build/            # lane worktrees + scratch (build/<id>-<lane>/)
  tmp/              # workspace-local temp β€” use this instead of /tmp

Run a command from anywhere inside a space and it just works β€” space and architect walk up from $PWD until they find the nearest space.yaml. No "current space" state to get out of sync; where you are is the space you mean. 🧭

Installation πŸ“¦

Add it to your Gemfile:

gem "space-architect"
bundle install

Or grab it yourself:

gem install space-architect

The gem is space-architect; it installs three executables β€” space, architect, and src. πŸŽ€

Quick start πŸŽ€

# Spaces
space init                                        # create XDG config + state files
space new "Name of Space"                         # blast off a new space πŸš€
space new "Name of Space" -r org/repo -r org/lib  # …with repos cloned in (repeat -r)
space list                                        # see all your spaces
space show                                        # show the space you're standing in

# Evergreen checkouts (copy-on-write sources for fast provisioning)
src repo add github.com/example-org/example-app     # tend it 🌲
src sync                                            # one sync pass
src status                                          # per-repo evergreen status

# Architect Loop (run from inside a space)
architect install-skills                          # install agent skills (once per machine)
architect init                                    # scaffold ARCHITECT.md + architecture/
architect new my-feature                          # scaffold the next iteration file
architect freeze my-feature                       # lock the Acceptance Criteria ❄️
architect dispatch my-feature lane-a              # send a headless builder to work

# Containers (run from inside a space) β€” a portable, reproducible-by-SHA image of the space
space pack                                         # render build/oci/ (Dockerfile + entrypoint)
space build                                        # pack + build & tag <space-id>:<git-sha>
space run                                          # run it β€” login shell, auth from your env

space β€” task-scoped workspaces 🌌

space init
space new "Name of Space"
space new "Name of Space" -r org/repo -r example-tools/alpha -r example-tools/beta
space list                                   # alias: space ls
space show 20260531-name-of-space
space path 20260531-name-of-space
space current                                # based on $PWD
space show                                   # based on $PWD
space status                                 # report: metadata + loop status (based on $PWD)
space status 20260531-name-of-space          # report another space
space status done                            # set: active | paused | done | archived
space status 20260531-name-of-space done
space config set default_provider github.com
space config set default_organization example-org
space repo add example-app                   # github.com/example-org/example-app
space repo add example-tools/alpha example-tools/beta
space repo add gitlab.com/example-org/api
space repo resolve example-app example-tools/async
space repo ls                                # alias: space repos ls
space use 20260531-name-of-space             # records recent state, prints the path
space ls --color=always                      # auto | always | never (--colors also accepted)

space status is report-or-set: with no status keyword (bare, or with just a space id) it reports the space β€” its metadata plus a compact loop-status block (project status, current iteration, derived state) when the space runs an Architect project, quietly omitted otherwise; pass a status keyword (active, paused, done, archived) to set it instead. πŸ”

Repos are passed with a repeatable -r flag (-r org/repo -r org/lib); the comma form (-r a,b) works too. Space ids are date-prefixed (20260531-name-of-space) so they sort naturally, and duplicate names on the same day get a counter (…-name-of-space-2). πŸ“…

Everything space does is also reachable as architect space … from within a project.

Containerize a space: pack Β· build Β· run πŸ“¦

A space is self-describing enough to become a container. space pack renders a portable OCI build context from the space; space build packs and builds it into a reproducible-by-SHA image; space run runs that image with your auth injected at runtime and stateful paths bind-mounted back to the host. 🐳

space pack                    # render build/oci/ (Dockerfile + entrypoint + ignore file)
space pack -o /tmp/ctx        # …to a different output directory
space build                   # pack, then build & tag <space-id>:<git-sha> and :latest
space run                     # run <space-id>:latest β€” login shell, auth from your env
space run architect status    # …or run a one-off command instead of the login shell
space run --tty               # force an interactive TTY (default: auto-detect)

What lands in the image. The context copies the whole space tree (filtered by a generated Dockerfile.dockerignore) onto a ruby:4.0.5 base with git, the Claude Code CLI, and the space-architect gem β€” installed from the in-space repos/space-architect checkout when present (a pinned build), else from RubyGems. Secrets never enter the layers: .env, *.key, *.pem, ssh keys, build/, and tmp/ are all excluded by the generated ignore file. πŸ”’

Reproducible by SHA. space build tags the image <space-id>:<sha>, where <sha> is the space repo's 12-char HEAD (suffixed -dirty when the tree has uncommitted changes), plus a moving :latest. Same commit β†’ same tag β†’ same image. It drives the container CLI, but the output is an ordinary OCI/Docker build context, so docker build -f build/oci/Dockerfile . (from the space root) works just as well.

Auth stays out of the layers. space run injects only the auth environment variables that are actually set β€” ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_BASE_URL β€” with -e at run time, so credentials live in your shell, never in the image. πŸ—οΈ

Forwarding payload credentials. Beyond the always-on auth trio, a space can declare run.env: in space.yaml β€” a list of host env var names forwarded into the container at run time. You can also pass space run --env VAR (repeatable) for ad hoc additions. All forwarding is bare -e VAR passthrough: values never appear in argv, ps, or the image. A requested-but-unset var warns on stderr instead of silently failing inside the guest.

run:
  env:                         # run-time: host var names, forwarded as bare -e VAR
    - FIREWORKS_API_KEY
    - OPENAI_API_KEY

Declaring provisioning & persistence β€” two optional keys in space.yaml:

pack:
  provision:                 # build-time: relative scripts, baked in by `space build`
    - scripts/setup-toolchain.sh
  persist:                   # run-time: absolute guest paths, bind-mounted per run
    - /root/.claude
    - /root/.local/state

provision scripts must live under the space root and be executable. Each script is copied into the image individually (COPY <script> /space/<script>) and then invoked (RUN /space/<script>) before the full space tree lands and before the gem is installed. This ordering is the cache-hygiene guarantee: editing any other space file leaves the provision and gem-install layers cached, so a rebuild completes in seconds instead of minutes. Scripts must therefore be self-contained β€” they run with only the base system layers and any earlier provision scripts' outputs; they cannot read other space files or call architect/space. Scripts must have the executable bit set; COPY preserves the mode from the build context.

persist paths must be absolute; space run bind-mounts each one from <space>/.state<path> on the host (created on first run) so a container's mutable state survives across runs. Both are validated at pack time.

Like everything else, these are reachable as architect space pack|build|run from inside a project.

src β€” the evergreen engine 🌲

src keeps local clones under <src_dir>/<host>/<owner>/<repo> (default ~/architect/src/…) clean, on their default branch, and freshly fetched β€” so spaces can provision repos by copy-on-write instead of cloning over the network.

src repo add github.com/example-org/example-app     # track + tend a repo
src org add github.com/example-org                  # track a whole org
src sync                                            # run one sync pass (--repo to scope)
src status                                          # per-repo evergreen status table
src clone example-app                               # APFS copy-on-write into $PWD
src daemon install                                  # per-user launchd agent for background sync

When space repo add finds a matching evergreen checkout, it copy-on-writes from it instead of hitting the network β€” instant provisioning. ⚑ The two surfaces share one layout by design, so they line up with zero configuration:

src repo add github.com/example-org/example-app   # tend it: keep it evergreen 🌲
space repo add example-app                          # copies it instantly ⚑

src has its own --plain / --json output modes for scripting, and its own fish integration (src shell fish install).

architect β€” the Architect Loop πŸ—οΈ

The Architect Loop is a structured build cycle for you and headless AI builders. Each loop lives inside a space as a project.

Roles:

  • Architect β€” the judgment role: a strong reasoning model (or you), run interactively. Arbitrates disagreements, writes and freezes iteration files, calls kill/continue, merges builder output. Never writes implementation code.
  • Builder β€” the execution role: a cheaper model run headless via architect dispatch, one per lane in its own git worktree. Reads the iteration's Builder Prompt, does the work, writes raw evidence to build/<id>-<lane>/report.md. Never grades its own work; never edits architecture/.

The loop is model-agnostic β€” which models fill the two roles is your choice (e.g. a strong Claude model judging a cheaper one on the same plan, or a cross-vendor pairing for more independent review). Set it per dispatch with architect dispatch --model …, or run several pairings head-to-head as a variant set (architect variant add). See docs/DESIGN.md Β§1–§2 for the reasoning.

Filesystem layout:

architecture/
  ARCHITECT.md              # cross-iteration index; project-wide state
  BRIEF.md                  # durable Β§-numbered project contract (optional)
  I01-<iteration>.md        # one self-contained file per iteration
build/
  I01-<iteration>-<lane>/   # lane worktree + scratch per dispatch
    run.jsonl               # streamed builder output
    report.md               # builder report (transcribed into the iteration file verbatim)

Iteration file anatomy β€” one file, grown section by section. You author the content in fresh scratch files; the CLI owns the persistence (each command writes the section, commits it, and prints back what changed). Every committing command takes -m/--message and --message-from <file> β€” your first line completes the subject after a short canonical prefix (I01 spec: <subject>), the rest becomes the body. The space's git log is the loop's durable memory: write detailed messages.

Section Holds How you persist it
## Grounds why β€” research / brief distilled (optional) architect section <it> grounds --from <f>
## Specification what/how β€” the full delegation contract architect section <it> specification --from <f>
## Acceptance Criteria proof β€” exact gate commands + thresholds architect freeze <it> ❄️
## Builder Prompt the exact lane-prompt(s) dispatched architect section <it> prompt --append --lane <l> --from <f>
## Builder Report raw evidence, transcribed verbatim architect evidence <it> --lane <l>
## Verdict rulings + per-AC PASS/FAIL + KILL/CONTINUE architect section <it> verdict --from <f>

The freeze ❄️ β€” architect freeze <iteration> commits the frozen region (Grounds / Specification / Acceptance Criteria), records the freeze_sha, and prints the frozen Acceptance Criteria back. Any change to those sections afterward is an automatic iteration FAIL. The builder never edits the iteration file.

Re-grounding 🧭 β€” architect init also scaffolds a SessionStart hook that runs architect ground (emitting ARCHITECT.md, BRIEF.md, and the in-flight iteration) so every fresh session starts oriented β€” the loop leans on fresh-session judgment, and this is what makes picking up cold cheap. Builders inside a lane worktree are never grounded.

Command surface:

architect init                              # scaffold ARCHITECT.md + the space.yaml project: block + SessionStart hook
architect brief new --from <f>              # write the durable project BRIEF.md (authored in a scratch file)
architect new <iteration>                   # scaffold architecture/I<NN>-<iteration>.md
architect section <it> <section> --from <f> # write + commit a section (add -m/--message-from for a detailed commit)
architect freeze <iteration>                # freeze the Acceptance Criteria ❄️
architect worktree add <repo> <it> <lane>   # isolated worktree per lane (2–4 lanes)
architect dispatch <it> <lane> --prompt <f> # copy the lane prompt in + dispatch a builder (--detach to survive long runs)
architect verify <iteration>                # post-flight mechanical checks (reports only)
architect evidence <it> --lane <lane>       # transcribe the builder's report verbatim
architect gate <iteration>                  # run the frozen gate commands, stream raw output
architect merge <it> <lane>                 # integrate ONE judged-passing lane (--no-ff)
architect integrate <it> --lanes a,b        # integrate a set of passing lanes, in order
# landing (PR body + push/PR command) is the architect's end-of-project procedure (see skill)
architect status                            # project state (read-only)
architect variant add|compare|promote …     # competing (harness, model) lanes over one frozen spec
architect research dispatch|status|wait …   # parallel read-only research lanes (see below)

architect help (or bare architect, architect --help) lists these grouped by loop phase β€” Spec Β· Build Β· Judge Β· Land Β· Project Β· Groups, in canonical order β€” and, inside an architect space, ends with a compact loop-status block (project status + current iteration) so you can see where you are in the loop. 🧭

A typical session:

architect init                                   # first time
architect new my-feature                         # scaffold I01-my-feature.md
architect section my-feature specification --from spec.md -m "pull-based dispatcher seam"
architect freeze my-feature                      # lock it ❄️
architect dispatch my-feature lane-a --prompt tmp/prompts/I01-lane-a.md --detach   # send a builder; poll the report
architect verify my-feature                      # mechanical post-flight checks
architect evidence my-feature --lane lane-a      # transcribe raw evidence
architect gate my-feature                        # run the frozen gates yourself
# … read the diff against the spec, then write the Verdict …
architect integrate my-feature --lanes lane-a    # merge passing lanes β†’ project/<slug>
# landing (PR body + push/PR command) is the architect's end-of-project procedure (see skill)

Streaming builder output πŸ“‘

architect dispatch can push the builder's stream-json to an ingest server for live viewing:

# Push to an already-created run (you supply the full ingest URL):
architect dispatch my-feature lane-a \
  --push-url   $HOST/runs/<id>/ingest \
  --push-token $INGEST_TOKEN

# Create a run and push in one step (requires --push-token = server's INGEST_TOKEN):
architect dispatch my-feature lane-a \
  --push-host  $HOST \
  --push-token $INGEST_TOKEN

--push-host POSTs to <HOST>/runs, parses the new run id from the 201 response, derives <HOST>/runs/<id>/ingest, and streams there; the created run id and ingest URL are printed after dispatch starts. --push-url and --push-host are mutually exclusive, both require --push-token, and neither can be combined with --detach (the push tees the live pipe in-process).

Research lanes πŸ”­

When an iteration needs facts the repo doesn't already have, fan out parallel read-only research lanes β€” detached claude -p researchers (no Edit/Write/Bash) that you supervise:

architect research dispatch 01-official-api.prompt.md 02-changelog.prompt.md
architect research wait        # tails each lane's run.jsonl; --level 1-4, --quiet, --thinking
architect research status      # status of dispatched runs

Researchers gather; the architect verifies the load-bearing claims against sources and writes the iteration's Grounds section.

Skills 🧠

architect install-skills installs the bundled architect, architect-research, and architect-vocabulary skills for your harness:

architect install-skills                         # default: claude (~/.claude/skills/)
architect install-skills --provider opencode     # or codex | pi
architect install-skills --project               # into ./… instead of globally
architect install-skills --dry-run               # show what would change

architect-vocabulary loads the system's terms and a short orientation when you're in a space but don't want to run the loop.

Fish shell integration 🐟

Shells can't let a child process change their working directory, so space and src each ship a small fish wrapper function plus completions (commands, subcommands, spaces, statuses, config keys, repo refs). Install into fish's autoloaded directories:

space shell fish install
src shell fish install
exec fish

After restarting fish (or exec fish), space new "…" and space use <id> will cd into the selected space once the command succeeds; every other command keeps normal CLI behavior. πŸšͺ The functions and completions are written under ~/.config/fish/, so there's no need to edit config.fish. For one-off testing without installing:

space shell init fish | source

Configuration βš™οΈ

Config lives at ~/.config/space-architect/config.yml (XDG-aware) and defaults to:

version: 1
base_dir: ~/architect            # spaces_dir + src_dir hang off this by default
default_provider: github.com
default_organization:
git_clone_protocol: ssh          # ssh | https

Derived defaults: spaces_dir β†’ <base_dir>/spaces, src_dir (evergreen checkout root) β†’ <base_dir>/src. Override either explicitly. View values with space config show; set one with space config set KEY VALUE. Editable keys: base_dir, spaces_dir, src_dir, default_provider, default_organization, git_clone_protocol.

Repos: evergreen, copy-on-write, concurrent ⚑

Repos are added to the current space under repos/ and tracked in space.yaml. When an up-to-date evergreen checkout exists at <src_dir>/<host>/<owner>/<name> (e.g. ~/architect/src/github.com/example-org/example-app), space copies it into the space instead of cloning over the network β€” a copy-on-write clone on APFS. ⚑ Set src_dir empty to always clone:

space config set src_dir ""

Clone URLs default to SSH (git@github.com:example-org/example-app.git); switch with space config set git_clone_protocol https. Multiple repos passed to space repo add are fetched concurrently, up to five at a time, on fibers β€” no threads, all cooperative. 🧡 After each repo lands, space runs mise trust in it. Each space also gets a workspace-local tmp/ β€” use it instead of /tmp.

Embedding πŸ“š

The library is split into three namespaces you can require independently:

  • Space::Core β€” the foundation: config, state, XDG, terminal, git/mise clients, the space store. The space CLI runs on this alone.
  • Space::Architect β€” project state, the builder harness, dispatch, and the research supervisor.
  • Space::Src β€” the evergreen engine (tracking, sync, copy-on-write clone).
require "space_core"       # just spaces
require "space_architect"  # the full loop (pulls in core + src)
require "space_src"        # just the evergreen engine

Documentation πŸ“–

  • Command Reference β€” every command, flag, and behavior
  • Design β€” the source-backed rationale: the twelve invariant rules (R1–R12), the failure-mode β†’ mitigation table, and why the loop is shaped this way
  • Changelog β€” release history

Development πŸ› οΈ

bundle install
bundle exec rake test       # the full minitest suite
bundle exec rake build      # build the gem into pkg/
bundle exec rake install    # build + install into your user gem home

Contributing πŸ’

Bug reports and pull requests are welcome on GitHub at https://github.com/jetpks/space-architect!

License πŸ“„

Available as open source under the terms of the MIT License.


Made with πŸ’– and fibers 🧡 by Eric