@kristoffer-karl-axel-ekstrand/office-space
v0.9.1
Published
Profile-driven, harness-neutral multi-agent workflow runtime for repository-local AI teams.
Downloads
1,925
Maintainers
Readme
@kristoffer-karl-axel-ekstrand/office-space
Profile-driven, harness-neutral multi-agent workflow runtime for repository-local AI teams.
Why office-space?
Why does office-space exist?
AI coding tools produce code fast when run unattended, but not code you can trust. Long runs and "vibe coding" sessions tend to end the same way: the happy path works, but the feature has not been tested or checked. A human must sit there, re-prompt, re-review, and re-test the same work. That is not less effort than writing it yourself. It is babysitting. office-space lets someone run a team pretty much unattended.
Who runs it?
Anyone who wants to produce software without watching every step: a solo developer building a side project, someone who enjoys building but not supervising an agent all day, or a team using AI on a real work project. If using AI ever felt like it gave you a different job — prompt, re-check, prompt again — instead of more freedom, this is for that problem.
Why use it instead of just running an AI CLI directly?
Running an AI CLI directly — Claude, Codex, Devin, or whatever comes next — gets you fast output and no structure: no independent review, no enforced tests, no memory of what was already tried and rejected, and no built-in coordination for multiple agents. office-space adds the missing structure — a task board with review and QA stages, path locks so agents don't collide, evidence requirements before anything is marked done, and health checks — so you can walk away and come back to real, checked progress instead of a mess to clean up.
It's built for more than a quick one-off prompt: hand it an epic or a technical spec for a large, repetitive change across a big codebase, and the team keeps grinding through it for hours or days without you in the loop. Roles can even run different models — one vendor implements, a genuinely different one reviews — so quality isn't just one model checking its own work.
The task board, progress, and mail are plain repo files, not state trapped in one vendor chat session. You can commit the workflow state, pause work when you hit token or rate limits, and resume later with another vendor or model from the recorded state.
(Runs natively on macOS/Linux; on Windows, use WSL2 or — our recommendation — the built-in dev container.)
Contents
- Why office-space?
- Quick start
- One-shot runners
- CLI
- System requirements
- Development setup
- Release check
- Portability
- License
Quick start
The package is dependency-free at runtime. A host repository supplies one or more workflow profiles
under .agents/office-space/<workflow>/ (each with a space.json). The CLI discovers the host by
walking upward from the current directory until it finds .agents/office-space/.
This repository also serves as a runnable example: .agents/office-space/ contains a sample
developer-team workflow that exercises the CLI and validation checks. Agent-facing commands and
boundaries live in AGENTS.md (with a CLAUDE.md @AGENTS.md shim for Claude Code).
npx @kristoffer-karl-axel-ekstrand/office-space initThis scaffolds a developer-team workflow with six roles (team-lead, dev-1, dev-2, scout,
quality, tests) under .agents/office-space/developer-team/, plus:
co-workers/<agent>.prompt.md+.prompt.json— one role body and one config file per agent, already wired to a role-appropriate set of shared doctrine partials (an implementer gets more than a reviewer, a reviewer gets more than the team lead)co-workers/_shared/*.md— generalized cross-cutting rules every role can opt into: messaging format, mailbox hygiene, memory discipline, git/commit boundaries, concurrency and locking, self-verification, spec discipline, and the plan-approval gate.agents/skills/office-space-protocol/SKILL.md— the canonical mail/Slack/tasks/memory protocol, generalized from a production multi-agent team and installed automatically so agents have real doctrine on first boot, not an empty folder- Root
AGENTS.md+CLAUDE.md(@AGENTS.mdshim) when missing — portable agent instructions so Claude and other harnesses share one policy file WORKFLOW.md— a starter mission doc with the section skeleton a real team needs (mission, co-workers, claiming/locks, pipeline, human gates, quality bars, bootstrapping, changelog); rewrite it to describe your own projectspec/,records/{checkpoints,decisions,retros,memory},epics/{drafts,todo}— the durable and semi-durable layers a team accumulates over time, pre-created so nobody invents their own convention later
None of the generated content mentions any specific product or domain — it is a starting point to edit, not a finished team.
After init
Init scaffolds folders and seeds package skills; it does not design your team. Prove the host, then redesign:
office-space check-system
office-space doctor
office-space sync --write
office-space check --tier fastThen rewrite WORKFLOW.md, tune co-workers, and replace seed T001 with a real first task. Init
installs these package-owned skills under .agents/skills/:
- Core workflow:
office-space-protocol,office-space,office-space-answer-mail, andmodel-chooser - Setup and team design:
office-space-onboard,office-space-team-design,agent-agnostic, andshare-skills - Authoring and integration:
create-skill,agent-cli-integration,tmux-automation,watch-and-improve-office-space, andtechnical-writing
The office-space-onboard and office-space-team-design skills provide guided setup sequences.
When doctrine is hard to open in the harness, print it with office-space explain onboard (or
team-design, protocol, …; office-space explain --list for topics).
When you're ready to actually run the team (needs bash, jq, tmux, realpath, timeout, flock, and sha256sum — see System requirements):
office-space team start
office-space team status
office-space team stopteam start opens a tmux session with one pane per agent plus a control pane, a health-monitor, and
a dashboard window; each agent launches with its fully composed prompt (role body + opted-in
partials + spec index) and begins its wake/work loop. The control pane lists every co-worker,
enabled and disabled, in one place. Select a row, then press t to toggle it on or off. Press s
to stop the whole team.
Intercom (wait/poller): between turns the harness (oneshot) or the agent (persistent) runs
intercom — the mailbox poller that watches slack//mail/, heartbeats, and wakes the next turn.
This is an internal process name, not Intercom.com. Canonical
binary: node …/intercom.js <workflow> <agent>. Compat: await-messages.js is a silent thin
forwarder for at least one package minor; stop/reap matches both basenames during that window. Lock
and heartbeat paths (.await-lock-*, health/heartbeats/) are unchanged.
You can inspect the running session from outside the CLI with tmux ls and
tmux attach -t <workflow>-agents. The health monitor runs in a dedicated monitor window
(src/run-team and src/lib/team-monitor.sh); each agent's intercom poller writes
health/heartbeats/<agent>.json every 30 seconds (src/intercom.js). The monitor uses heartbeat
age, pane command, and poller state to classify genuinely stagnant agents and calls restart_agent
(src/run-team) to respawn their panes. Heartbeats carry pid and startTime so a reused OS pid
is not mistaken for a live agent, and the .temp/.await-lock-* files keep only one poller per
mailbox after a restart.
Keeping current
After bumping the office-space package, do not use init --force to upgrade skills or partials
(too broad; clobbers workflow content). Read host notes, refresh package-owned skills and partials,
then re-bridge discovery:
office-space whats-new # host notes since host stamp (semver); --all / --from VER
office-space skills refresh --check # package-owned skills that differ from bundled templates
office-space skills refresh --write # updates package-owned only; optional --notify → mail/in digest
# multi-workflow hosts (this monorepo): pin product; bare check also scans fixture workflows
office-space partials refresh --check --workflow developer-team
office-space partials refresh --write --workflow developer-team
office-space sync --write
office-space doctorBare partials refresh --check scans every host workflow with space.json (product plus fixtures
such as demo and sync-check on this monorepo). Use --workflow <product> when you only need the
product workflow current. Use office-space explain <topic-or-host-skill-name> when agents cannot
see skill bodies after a bump. Host-owned skills under .agents/skills/ and host-only _shared
basenames are never overwritten by refresh.
Host-facing upgrade log: package templates/host-upgrades.md (this repo:
src/templates/host-upgrades.md). That file is what whats-new prints. Setup-affecting releases
must add a ## X.Y.Z section there before publish. The release-qualification driver rejects a
newest section that differs from the package version; this is not an npm pack check.
One-shot runners
team start is for resident co-workers that watch mail and pick up tasks continuously. For bounded
maintenance jobs use a runner under runners/<runner-id>.prompt.json + <runner-id>.prompt.md
with runner: true and start_mode: "one-shot" (one-shot = job bounds: authored prompt only,
turn/time budget — not “no host”).
Job contract (E003 / D006):
- Invoke with
office-space run <runner-id> [--workflow <name>](no ad-hoc prompt override), or post atype: run-requestaddressed to the runner id inmail/in/. - Defaults: 5-turn budget and 15-minute wall-clock timeout (
max_turns/timeout_minutes). - Runners claim
owned_pathsand followqueued → ... → doneunless the entire diff is a single deterministic, idempotent formatter invocation that passes a formatter-canonical text comparison and a syntax check. That fast-path lands intasks/done/withhealth/runner-runs/<run-id>/run.json.
Team-hosted (live — D007 scaffolding):
team startopens arunnerswindow: one pane per runner plus a controller.- When the team is up,
run-requestmail is queued via the controller path (queueRunnerRequest); the leadintercomloop does not block on the full job. - Default
office-space run <runner-id>also enqueues on that same durable path when{workflow}-agentsis up and the runner pane + controller exist — returns after queue write, no dual inline execution. - Pending queue files live under
health/runner-requests/(not.temp/) so a team restart does not drop undelivered work; activation/busy markers under.temp/remain ephemeral. - Synchronous / headless path when the team is down, the runner is not pane-hosted, or for CI: use
office-space run … --foregroundor--headless(forcesrun_one_shot; no tmux pane kept open by the CLI alone).
Set-time schedule (controller-owned):
"schedule": {
"tz": "UTC",
"times": ["09:00", "13:00", "18:30"]
}- The controller pane fires each listed
HH:MMonce per calendar day while the team is up (timezone fromschedule.tz, default UTC). - Last-fired state lives under
health/runner-schedule/<runner>.jsonso a restart does not re-fire the same slot. - Busy or path-conflict (working-on overlap) delays within the minute; once the clock moves on, the slot is a miss — no backlog catch-up.
- Empty/missing
times→ on-demand only (run-request/office-space run). No full cron in v1.
CLI
office-space init
office-space init -h
office-space init --help
office-space init --preset thin
office-space init --preset full --workflow my-team
office-space init --workflow my-team --agents lead,dev-1,reviewer --agent-roles "lead=lead,dev-1=implementer,reviewer=reviewer+qa"
office-space init --harness claude
office-space init --force
office-space init --force --clobber-default-mission
office-space check-system
office-space doctor
office-space explain [TOPIC] | --list
office-space whats-new [--all|--from VER]
office-space skills refresh --check|--write [--notify]
office-space partials refresh --check|--write [--workflow NAME]
office-space sync --check
office-space sync --write
office-space check --tier fast
office-space check --tier fast my-team
office-space check --tier full
office-space team start developer-team
office-space team stop developer-team
office-space team status developer-team
office-space team drain developer-team
office-space team roles developer-team
office-space run <runner-id> [--workflow <name>] [--foreground|--headless]
office-space collaboration-digest [WORKFLOW] [--write] [--json]
office-space evidence-audit <command> [<workflow> <task-id>]
office-space state checkpoint developer-team
office-space state checkpoint developer-team --label pre-release
office-space state restore developer-team
office-space state compact developer-team
office-space lock --resource package.json -- npm test
office-space lock --workflow my-team --lane release --resource package.json -- npm test
office-space models audit [--live] [--include-rows] [--strict] [--catalog PATH] [--registry PATH]
office-space models propose [--roles PATH] [--output PATH] [--workflow NAME] [--catalog PATH] [--registry PATH]
office-space models apply PROPOSAL [--confirm] [--workflow NAME] [--catalog PATH] [--registry PATH]
office-space ctrlmodels runs as a sequence. audit inspects the model catalog and classification. Use --live for
optional live availability. audit supports --include-rows for detailed output and --strict for
stricter validation.
propose drafts a model assignment proposal from role requirements. apply validates a chosen
proposal by default. Use --confirm to write into the workflow's co-worker prompts.
init scaffolds a workflow profile (space.json) under .agents/office-space/. It also creates
role prompts, specs, and all required runtime directories in an empty repository.
Use --force to re-scaffold an existing workflow folder when it is not the default workflow with
customized mission content. Non-default workflows are fine. A default workflow still on template
content is also fine.
The command refuses a default workflow with customized mission content (WORKFLOW.md and/or
approved-class specs) under bare --force. Add --clobber-default-mission only when you want to
destroy WORKFLOW, co-worker prompts, space.json, and spec/*.
Path upgrades still use skills/partials refresh (see Keeping current), not
init --force.
Greenfield verification is intentionally repository-neutral. space.json starts with only the
portable workflow-integrity machine check.
Before using machine review for code changes, define the repository's real test, lint, build, or
package commands in review.machineChecks. Describe the required evidence ladder in the workflow's
WORKFLOW.md.
Presets: --preset thin scaffolds lead, implementer, and combined reviewer/qa. --preset full
is the default six-agent map with an explicit name. Omitting --preset keeps today's default
lineup. Do not pass --preset together with --agents or --agent-roles. This is an error. There
is no solo preset.
check
check --tier fastrunsdoctor(workflow profile and adapter validation) pluscheck-workflow(references, specs, tasks, and messages). This is the default CI gate for a working repository.check --tier fullruns the same gates as fast and then runs the defaultcheck-systemgate and any host-defined gates. Use it before a release or after changing the host environment.
Host-defined gates live in .agents/office-space/check-manifest.json (one-release fallback:
.agents/office-space/space.json with a deprecation warning). This file is optional and separate
from the per-workflow space.json profile. It may contain freshness and verification.full
arrays of command specs.
The per-workflow .agents/office-space/<workflow>/space.json profile does not accept freshness or
verification keys. doctor rejects a workflow profile that contains them with a path-named error
naming the workflow file and the unknown property.
A command spec is either an argv array of non-empty strings or an object with a non-empty name and
a non-empty command array. For example:
{
"freshness": [{ "name": "build", "command": ["npm", "run", "build"] }],
"verification": {
"full": [{ "name": "integration", "command": ["npm", "run", "test:unit:integration"] }]
}
}Hosts may use their own script names in this file. The sample above matches this package's
test:unit:integration script (heavy run-team / tmux / poller coverage).
Malformed top-level shape (bad JSON, wrong types) is warned and ignored: check --tier full falls
back to the default check-system gate. Malformed individual entries exit non-zero with a
path-named error such as
.agents/office-space/check-manifest.json: verification.full[0] requires a non-empty command array and non-empty name.
collaboration-digest prints board throughput and coordination metrics. It shows stalls and reject
budget signals. It does not show health or uptime. Use --write to store a snapshot under the
workflow's live health/ tree.
explain prints a skill body. Host install first, else package template.
whats-new prints host-facing notes from package templates/host-upgrades.md. Default shows
sections with version > host stamp. No stamp shows latest only.
--all prints the full notes file. --from VER uses an exclusive floor (sections with version >
VER). --all and --from VER are mutually exclusive. whats-new does not update the host stamp.
skills refresh diffs or updates package-owned skills only. --check exits nonzero when any
package-owned file is stale or missing. --write prints the check summary first. --write updates
the host-meta stamp.
Keep path after skills refresh: partials refresh --check|--write [--workflow NAME] →
sync --write → doctor. On multi-workflow hosts, pass --workflow for the product workflow.
--notify (with --write only) drops an opt-in lead digest under mail/in/.
partials refresh diffs or updates package-owned co-workers/_shared partials. The command never
modifies host-owned basenames. Default applies to every host workflow with space.json (product and
fixtures). --workflow NAME limits to one. The command reports per workflow path.
On monorepos with fixture workflows, prefer --workflow for product-only check and write. Bare
--check fails when any fixture partial is stale or missing even if the product workflow is clean.
Bare --write can clobber intentional fixture drift. --write prints the check summary first.
--write does not stamp host-meta. --check exits nonzero when any package-owned partial is stale
or missing.
Package templates serve as the source of truth for consumer hosts. This repo's dogfood _shared may
differ until refresh/promote.
sync --write bridges discovery adapters. It handles skills, personas, and commands.
sync --write --mirror creates deterministic generated mirrors for Windows checkouts without
symlink support. See Keeping current.
When supplying custom --agents, include one --agent-roles entry for every listed agent. Entries
use the agent=role+role syntax shown above. Supported roles are lead, implementer, reviewer,
qa, and none.
Optional specialty prompts (architect, researcher, product, tech-writer, security,
release) live under package templates/co-worker-roles/. They are not in thin/full presets.
Include the id with matching flags (usually =none) or copy onto an existing workflow.
Pipeline handoffs use task-transition with capability tokens, not conventional agent names.
--assignee auto-implementer selects the least-loaded enabled implementer: true agent (stage
implementing only). --assignee auto-reviewer and --assignee auto-qa do the same for
reviewer: true at review and qa: true at qa. Staffed moves into implementing require
implementer: true (lead and specialists are not on that allowlist).
--harness sets which CLI every scaffolded agent defaults to. Options are claude, codex,
devin, copilot, cursor, antigravity, and grok. The default is devin. The command writes
the harness into each agent's <agent>.prompt.json as cli.
Only devin needs an explicit default model. Every other harness picks its own default when you
omit model. init omits it for those. grok is oneshot-capable (like claude and devin).
team drain writes a durable checkpoint. It blocks every transition into implementing while the
drain marker exists (not only queued→implementing; blocked re-claims and review/qa bounces wait for
team start). Already-implementing work may still advance. team start restores required runtime
directories. It clears the drain marker and starts the configured workflow.
state compact checkpoints first. Then it applies the workflow's Slack, completed-task, and
health-report retention limits from space.json state.retention (including healthReports,
default 3). Continuous health-report writes use the same state.retention.healthReports keep
count and prune only [HEALTH-REPORT]-* files; compact may still thin every file under
health/reports/ by mtime. Invalid or non-finite healthReports values default to 3 (not
wipe-all). state checkpoint accepts an optional --label NAME to name the checkpoint.
state restore only ensures the runtime directory layout (mail/tasks/slack/health/memory/.temp); it
does not load or apply checkpoints. lock accepts optional --workflow NAME and --lane NAME to
target a specific workflow and build lane. ctrl launches the interactive team dashboard. The
command requires a TTY.
Evidence audit
evidence-audit invalidates and restores the evidence behind a done task without reopening it.
Workflow and task-id are required for invalidate, run, restore, and repair-projection. They
are optional on the summary form because path-only gates (scoped-format, scoped-lint) take paths
instead. Full subcommands: office-space evidence-audit --help.
stage: done stays terminal; the audit appends an invalidation/restoration ledger to the task
record and updates the task projection.
Use it when a previously passing task needs to be re-checked: the configured lead invalidates the old evidence, QA re-runs the required tier gates, and QA restores verification only when the replacement runs match the current source fingerprint and are no older than the 60-minute reuse window at the moment of restoration.
Only the single configured lead may invalidate or repair-projection. Only configured QA may
run and restore.
System requirements
The runtime scripts need bash 4+, jq 1.5+, tmux 3.0+, realpath, timeout,
sha256sum (from coreutils), and flock (from util-linux). Shell entrypoints use
realpath for SCRIPT_DIR; run-team / cli-tools hard-require timeout for wall-clock probes;
path locks use sha256sum + flock; global / integration locks also call flock. Run
office-space check-system and it will print the right install command for your platform, or
install them manually:
# Debian / Ubuntu / WSL2
sudo apt update && sudo apt install -y bash jq tmux coreutils util-linux
# RHEL / Fedora
sudo dnf install -y bash jq tmux coreutils util-linux
# Arch Linux
sudo pacman -S bash jq tmux coreutils util-linux
# macOS / Homebrew
brew install bash jq tmux coreutils flock
# put GNU realpath/timeout/sha256sum first if only grealpath/gtimeout/gsha256sum are on PATH:
# export PATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH"
# macOS / MacPorts
sudo port install bash jq tmux coreutils
# flock is util-linux (not coreutils); install a flock/util-linux port if missing
# Windows (requires WSL2)
wsl --install
wsl -e sudo apt update && wsl -e sudo apt install -y bash jq tmux coreutils util-linuxWSL2 is required (not WSL1). tmux needs a real Linux kernel/pty layer; WSL1 is a compatibility shim and is unreliable for tmux.
check-system also verifies the Node.js version (≥ 22.12) and that realpath, timeout, flock,
and sha256sum are on PATH.
Canonical skills can be checked against the Agent Skills specification with:
npm run validate:skillsDevelopment setup
Using the devcontainer is optional, but we strongly recommend it. If you open the repo in the
devcontainer, it uses the mcr.microsoft.com/devcontainers/typescript-node:4-24-trixie image and a
postCreateCommand that installs jq, tmux, shellcheck, shfmt, and runs npm install for
you. Agents run with full/elevated permissions inside the container; the devcontainer is the
containment boundary, so that elevated runtime stays inside the container rather than on the host.
It does not add extra sandboxing beyond the normal Dev Containers workspace mount and
network/filesystem exposure.
On Windows, the devcontainer runs via Docker Desktop's WSL2 backend — choosing the devcontainer
is itself a WSL2 path (bundling tmux/jq/shellcheck/shfmt) versus bare wsl --install plus manual
tool installation.
To develop locally, first satisfy the System requirements, then add the lint/test tools:
# Debian/Ubuntu/WSL2
sudo apt-get install -y shellcheck
# shfmt
curl -fsSL -o /tmp/shfmt https://github.com/mvdan/sh/releases/download/v3.11.0/shfmt_v3.11.0_linux_amd64
sudo install -m 755 /tmp/shfmt /usr/local/bin/shfmtRun office-space check-system (or npm run check:system from the repo) to verify bash, jq, tmux,
realpath, timeout, flock, sha256sum, and Node. Then install Node dependencies:
npm installRelease check
npm run verifynpm run verify runs formatting, linting, skill validation (validate:skills), all unit tests
(including run-team integration), the global-lock self-test, the runtime self-test, workflow checks,
and a dry-run pack of dist/ (npm run npm:pack). CI runs the same command on every push and pull
request. To re-check the pack alone after npm run build, run npm run npm:pack (not bare
npm pack from the repo root). Publish with npm run npm:publish / npm publish dist/ — never
root npm publish.
Day-to-day: npm run test:unit is the fast suite (excludes *.integration.test.js and
test/release/** E005 soaks). Prefer npm run test:unit -- <file> while editing. Use
npm run test:unit:integration when changing run-team / tmux / poller / runner-controller behavior,
npm run test:unit:all for fast+integration, and npm run test:unit:release only for E005
qualification.
Portability
Repository paths, workflow names, task stages, message types, state retention, build lanes, and adapter mappings belong in configuration.
Local discovery adapters use directory symlinks on Unix. sync --write --mirror creates
deterministic generated mirrors for Windows checkouts without symlink support. The command refuses
to replace unmanaged content.
License
MIT — see LICENSE.
