Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Run `curl -sfL install.lowkey.run | bash` — the installer walks you through **
| **NemoClaw** *(experimental)* | OpenClaw in NVIDIA OpenShell sandbox — Landlock + seccomp + netns isolation, Bedrock via bedrockify. `personal_assistant` profile only. | t4g.xlarge required | 80GB |
| **Kiro CLI** *(experimental)* | AWS agentic IDE terminal client with MCP server support. Uses own cloud inference (not Bedrock). Requires interactive SSO login after deploy. | t4g.medium sufficient | None needed (set to 0) |
| **Codex CLI** *(experimental)* | OpenAI's Codex coding agent — uses OpenAI API directly (no Bedrock). Configured as builder agent (danger-full-access). **After install completes**, SSM into the instance and run `codex login`. | t4g.medium sufficient | None needed (set to 0) |
| **Troika** *(experimental)* | Three-harness coding machine — OpenClaw/Hermes + Claude Code + Codex CLI, all via Amazon Bedrock. Pick a daily driver to auto-launch on SSM login; switch with `agents driver <name>`. Builder-only. Requires Anthropic + Bedrock Mantle model access. | t4g.xlarge required | 80GB |

The installer discovers packs dynamically and asks which to deploy. Experimental packs are clearly marked.

Expand Down Expand Up @@ -203,6 +204,7 @@ Lowkey uses a **pack-based architecture** for deploying different AI agent runti
| `nemoclaw` | Agent *(experimental)* | NemoClaw — OpenClaw inside an [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell) sandbox with Landlock, seccomp, and network namespace isolation. Inference routed through bedrockify on the host (no NVIDIA API key needed). **Only compatible with `personal_assistant` profile** — the sandbox blocks all AWS API access. Requires Docker + t4g.xlarge. |
| `kiro-cli` | Agent *(experimental)* | [Kiro CLI](https://kiro.dev/docs/cli) — AWS agentic IDE terminal client with MCP server support. Uses its own cloud inference (no Bedrock/bedrockify). Pre-installs AWS MCP servers (terraform, ecs, eks, core, docs). **Requires interactive SSO login after deploy:** `kiro-cli login --use-device-flow`. |
| `codex-cli` | Agent *(experimental)* | [Codex CLI](https://github.com/openai/codex) — OpenAI's coding agent. Connects directly to OpenAI's API (no Bedrock/bedrockify). Configured as builder agent (danger-full-access, never prompts). **Post-install step**: SSM into the instance and run `codex login` (interactive) — auth is intentionally deferred until after deploy. |
| `troika` | Agent *(experimental)* | Three-harness coding machine: OpenClaw/Hermes + Claude Code + Codex CLI on one instance, all via Bedrock. Pick a **daily driver** that auto-launches on SSM login; switch anytime with `agents driver <name>`. Builder-only. Requires Anthropic model access + Bedrock Mantle access (`openai.gpt-5.5`). |
| `roundhouse` | Agent | [Roundhouse](https://github.com/inceptionstack/roundhouse) — Pi-based AI coding agent with Telegram channel. Uses native Bedrock SDK (no bedrockify). Requires `TelegramBotTokenSecret` and `TelegramUser` parameters. |

### How It Works
Expand Down
164 changes: 122 additions & 42 deletions TRIFECTA-PLAN.md → TROIKA-PLAN.md

Large diffs are not rendered by default.

314 changes: 314 additions & 0 deletions TROIKA-REVIEW.md

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions TROIKA-STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# TROIKA-STATUS.md — Phases 5 & 6 Implementation Status

**Date:** 2026-07-12
**Implementer:** Subagent (depth 1)
**Phases completed:** 5 (Preflight Codex-model probe) + 6 (Docs)

---

## Phase 5: Preflight Codex-model probe

### What was implemented

**File modified:** `install.sh`

Two additions to `install.sh`:

1. **`_check_codex_model_access()` helper function** (placed after `_bedrock_access_guidance`, before `check_vpc_quota`, ~line 1460):
- Takes `codex_model` and `probe_region` as arguments
- **Attempt 1**: Tries `aws bedrock-runtime converse` — covers the case where Mantle does support converse, and catches ThrottlingException/TooManyRequests (throttled = access granted)
- **Attempt 2 (fallback)**: `aws bedrock list-foundation-models --query "modelSummaries[?contains(modelId, 'openai')]"` — confirms Mantle models are visible in the region/account
- If fallback also returns nothing: issues a non-fatal warning with model-access console URL
- Does NOT call `confirm_or_abort` — purely informational (user already saw Sonnet warning)
- Does NOT modify `BEDROCK_ACCESS_OK` — that global tracks Sonnet only

2. **Troika probe trigger** (added at end of `check_bedrock_access()`, before closing `}`):
- Condition: `PACK_NAME=troika` OR `PRESELECT_PACK=troika` OR `TROIKA_CODEX_MODEL` non-empty
- Rationale: `PACK_NAME` is still "openclaw" (default) at preflight time because pack selection runs later (`run_config_and_review`). `PRESELECT_PACK` covers `--pack troika` non-interactive mode. `TROIKA_CODEX_MODEL` covers `--codex-model` explicit overrides.
- Uses `${TROIKA_CODEX_MODEL:-openai.gpt-5.5}` as the model to probe

### Shellcheck

- Pre-change: 18 output lines (3 pre-existing warnings, all unrelated to Troika)
- Post-change: 18 output lines (same — zero new warnings introduced)
- Exit code: 0 (shellcheck -S warning)

---

## Phase 6: Docs

### Files created/modified

**New file:** `docs/agents/troika.mdx`

Content covers:
- What Troika is (three-harness coding machine: OpenClaw/Hermes + Claude Code + Codex CLI, all via Bedrock)
- Summary table of the three horses with binaries and roles
- Daily driver switching (`agents driver <name>`, `agents` status, escape hatches)
- Review workflow concept (manual cross-review commands; `agents review` stretch goal noted)
- Stack requirements table (t4g.xlarge, 80GB data volume, builder-only, region requirements)
- Bedrock model access section (two separate grants required: Anthropic Sonnet + Mantle openai.gpt-5.5)
- What the pack installs (6 steps)
- Resource usage notes
- First-run commands
- Links to related docs

**Modified:** `README.md`

Two tables updated:
1. **"Agent packs" summary table** (~line 89): Added `| **Troika** *(experimental)* | ...` row after Codex CLI
2. **"Available Packs" reference table** (~line 207): Added `` | `troika` | Agent *(experimental)* | ... `` row after `codex-cli`, before `roundhouse`

---

## Test Results

### `bash packs/troika/test.sh`

```
Passed: 59
Failed: 0
Skipped: 0
✓ All tests passed
```

### `bash tests/test-pack-contracts.sh`

```
Discovered 7 agent pack(s): claude-code codex-cli hermes kiro-cli openclaw roundhouse troika

Passed: 155
Failed: 0
Skipped: 0
✓ All pack contracts satisfied
```

---

## Code quality (§12b compliance)

- ✅ DRY: `_check_codex_model_access` is a single helper, called once from `check_bedrock_access`
- ✅ Single source of truth: probe trigger checks all known early signals (`PACK_NAME`, `PRESELECT_PACK`, `TROIKA_CODEX_MODEL`)
- ✅ Non-fatal by construction: no `confirm_or_abort`, no `fail`, `BEDROCK_ACCESS_OK` unchanged
- ✅ Shellcheck-clean at `-S warning` (zero new warnings)
- ✅ No hardcoded pack logic in troika pack files — only `install.sh` (main installer) was modified
47 changes: 44 additions & 3 deletions deploy/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ LITELLM_KEY=""
LITELLM_MODEL=""
PROVIDER_KEY=""
SKIP_TELEMETRON="false"
# Troika-specific params (primary horse, daily-driver, codex model — §12a.1, §12b)
PRIMARY=""
DAILY_DRIVER=""
CODEX_MODEL=""
# Pack-specific: optional Secrets Manager id/arn to resolve at install time
# into KIRO_API_KEY inside the kiro-cli pack (and potentially others later).
# The raw key is NEVER written to CFN state, UserData, or bootstrap logs.
Expand Down Expand Up @@ -216,6 +220,21 @@ while [[ $# -gt 0 ]]; do
TELEGRAM_USER="$2"
shift 2
;;
--primary)
[[ $# -gt 1 ]] || { echo "ERROR: --primary requires a value" >&2; exit 1; }
PRIMARY="$2"
shift 2
;;
--daily-driver)
[[ $# -gt 1 ]] || { echo "ERROR: --daily-driver requires a value" >&2; exit 1; }
DAILY_DRIVER="$2"
shift 2
;;
--codex-model)
[[ $# -gt 1 ]] || { echo "ERROR: --codex-model requires a value" >&2; exit 1; }
CODEX_MODEL="$2"
shift 2
;;
--*)
# Skip unknown options (with optional value)
if [[ $# -gt 1 ]] && [[ "$2" != --* ]]; then
Expand Down Expand Up @@ -256,6 +275,9 @@ jq -n \
--arg telegram_bot_token_secret "$TELEGRAM_BOT_TOKEN_SECRET" \
--arg telegram_user "$TELEGRAM_USER" \
--arg skip_telemetron "$SKIP_TELEMETRON" \
--arg primary "$PRIMARY" \
--arg daily_driver "$DAILY_DRIVER" \
--arg codex_model "$CODEX_MODEL" \
'{pack:$pack, profile:$profile, region:$region, model:$model, gw_port:$gw_port,
model_mode:$model_mode, bedrockify_port:$bedrockify_port,
hermes_model:$hermes_model, litellm_url:$litellm_url,
Expand All @@ -264,7 +286,10 @@ jq -n \
"from-secret":$from_secret,
telegram_bot_token_secret:$telegram_bot_token_secret,
telegram_user:$telegram_user,
"skip-telemetron":$skip_telemetron}' > "${PACK_CONFIG}"
"skip-telemetron":$skip_telemetron,
primary:$primary,
"daily-driver":$daily_driver,
"codex-model":$codex_model}' > "${PACK_CONFIG}"
chmod 600 "${PACK_CONFIG}"
chown ec2-user:ec2-user "${PACK_CONFIG}"
export PACK_CONFIG
Expand Down Expand Up @@ -323,6 +348,22 @@ registry_get_deps() {
" "$REGISTRY"
}

# get_effective_deps PACK — registry_get_deps with troika primary-substitution (§12a.1, §12b).
# When PACK_NAME=troika and PRIMARY≠openclaw, replaces the 'openclaw' dep with the
# selected primary. Applied to BOTH step-counting and install dispatch via this single
# helper (not two copies — §12b). No-op when primary=openclaw or pack≠troika.
get_effective_deps() {
local pack="$1"
local dep
while IFS= read -r dep; do
if [[ "$pack" == "troika" && "${PRIMARY:-openclaw}" != "openclaw" && "$dep" == "openclaw" ]]; then
echo "${PRIMARY}"
else
echo "$dep"
fi
done < <(registry_get_deps "$pack")
}

# registry_get_data_vol PACK — prints data_volume_gb value or "80" default
registry_get_data_vol() {
local pack="$1"
Expand All @@ -344,7 +385,7 @@ _total_steps=$(_count_steps_in "${DEPLOY_DIR}/bootstrap.sh")
while IFS= read -r _dep; do
[[ -n "$_dep" ]] && _dep_script="${PACKS_DIR}/${_dep}/install.sh" && \
[[ -f "$_dep_script" ]] && _total_steps=$((_total_steps + $(_count_steps_in "$_dep_script")))
done < <(registry_get_deps "${PACK_NAME}")
done < <(get_effective_deps "${PACK_NAME}")

# Add steps from main pack
_pack_script="${PACKS_DIR}/${PACK_NAME}/install.sh"
Expand Down Expand Up @@ -615,7 +656,7 @@ step "Phase 2: Pack Dispatch"
DEPS=()
while IFS= read -r dep; do
[[ -n "$dep" ]] && DEPS+=("$dep")
done < <(registry_get_deps "${PACK_NAME}")
done < <(get_effective_deps "${PACK_NAME}")

info "Pack: ${PACK_NAME}"
if [[ ${#DEPS[@]} -gt 0 ]]; then
Expand Down
29 changes: 27 additions & 2 deletions deploy/cloudformation/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Metadata:
- KiroFromSecret
- TelegramBotTokenSecret
- TelegramUser
- Primary
- DailyDriver
- CodexModel
- RequestQuotaIncreases
- Label:
default: "🛡️ Security Services"
Expand Down Expand Up @@ -124,7 +127,8 @@ Parameters:
- kiro-cli
- codex-cli
- roundhouse
Description: "Agent pack to deploy. 'openclaw' is the stateful AI agent with 24/7 gateway (recommended). 'roundhouse' is the pi-based agent with Telegram. 'hermes' is the NousResearch CLI agent (lighter). 'kiro-cli' and 'codex-cli' are experimental."
- troika
Description: "Agent pack to deploy. 'openclaw' is the stateful AI agent with 24/7 gateway (recommended). 'roundhouse' is the pi-based agent with Telegram. 'hermes' is the NousResearch CLI agent (lighter). 'kiro-cli' and 'codex-cli' are experimental. 'troika' installs OpenClaw/Hermes + Claude Code + Codex CLI on one instance (experimental)."

ProfileName:
Type: String
Expand Down Expand Up @@ -264,6 +268,24 @@ Parameters:
Default: ''
Description: "Telegram username for bot pairing (roundhouse pack only, without @ prefix)."

Primary:
Type: String
Default: openclaw
AllowedValues:
- openclaw
- hermes
Description: "Troika pack only: the OpenClaw-family first horse (openclaw or hermes). Ignored for other packs."

DailyDriver:
Type: String
Default: ''
Description: "Troika pack only: agent to auto-launch when you SSM into the instance (openclaw, hermes, claude-code, codex-cli, none; default=primary). Ignored for other packs."

CodexModel:
Type: String
Default: 'openai.gpt-5.5'
Description: "Troika pack only: Bedrock Mantle model ID for Codex CLI. Ignored for other packs."

EnableBedrockForm:
Type: String
Default: 'false'
Expand Down Expand Up @@ -1143,7 +1165,10 @@ Resources:
--provider-api-key "$PROVIDER_API_KEY" \
--kiro-from-secret "$KIRO_FROM_SECRET" \
--telegram-bot-token-secret "$TELEGRAM_BOT_TOKEN_SECRET" \
--telegram-user "$TELEGRAM_USER"
--telegram-user "$TELEGRAM_USER" \
--primary "${Primary}" \
--daily-driver "${DailyDriver}" \
--codex-model "${CodexModel}"
# Signal CFN success — defense-in-depth: bootstrap.sh also signals,
# but this catches edge cases where bootstrap.sh signal fails silently.
# CFN ignores duplicate signals, so double-fire is safe.
Expand Down
104 changes: 104 additions & 0 deletions deploy/test-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,110 @@ else
fi
echo ""

# ── Test 7: Troika dep-substitution ──────────────────────────────────────────
info "Test 7: get_effective_deps troika primary-substitution"
# Verify get_effective_deps is present and implements the substitution logic.
# We source only the registry helpers + get_effective_deps from bootstrap.sh;
# the rest of the script is not executed.

REGISTRY="${DEPLOY_DIR}/../packs/registry.yaml"
if [[ ! -f "$REGISTRY" ]]; then
info " Registry not found — skipping dep-substitution test"
else
# Extract get_effective_deps function + its dependency (registry_get_deps) to a temp file
_DEP_TEST_SCRIPT=$(mktemp /tmp/test-dep-subst-XXXXXX.sh)
cat > "$_DEP_TEST_SCRIPT" << 'DEPTEST_EOF'
#!/bin/bash
set -euo pipefail
REGISTRY="$1"
PRIMARY="${2:-openclaw}"

# Minimal stubs for bootstrap helpers bootstrap.sh needs before registry helpers
registry_get_deps() {
local pack="$1"
awk "
/^ ${pack}:/{found=1; in_deps=0; next}
found && /^ [a-z]/{exit}
found && /^ deps:/{in_deps=1; next}
found && in_deps && /^ - /{gsub(/^ - /, \"\"); print; next}
found && in_deps && !/^ /{in_deps=0}
" "$REGISTRY"
}

get_effective_deps() {
local pack="$1"
local dep
while IFS= read -r dep; do
if [[ "$pack" == "troika" && "${PRIMARY:-openclaw}" != "openclaw" && "$dep" == "openclaw" ]]; then
echo "${PRIMARY}"
else
echo "$dep"
fi
done < <(registry_get_deps "$pack")
}

# Test 1: troika + primary=openclaw → no substitution
PRIMARY="openclaw"
DEPS_OC=$(get_effective_deps troika | tr '\n' ',')
if [[ "$DEPS_OC" == *"openclaw"* ]]; then
echo "PASS: primary=openclaw → openclaw dep preserved"
else
echo "FAIL: primary=openclaw should preserve openclaw dep; got: $DEPS_OC"
exit 1
fi

# Test 2: troika + primary=hermes → openclaw replaced with hermes
PRIMARY="hermes"
DEPS_HERMES=$(get_effective_deps troika | tr '\n' ',')
if [[ "$DEPS_HERMES" == *"hermes"* && "$DEPS_HERMES" != *"openclaw"* ]]; then
echo "PASS: primary=hermes → hermes dep substituted, openclaw removed"
else
echo "FAIL: primary=hermes substitution wrong; got: $DEPS_HERMES"
exit 1
fi

# Test 3: non-troika pack unaffected by PRIMARY!=openclaw
PRIMARY="hermes"
DEPS_OC_PACK=$(get_effective_deps openclaw | tr '\n' ',')
# openclaw's own dep list should be unchanged
if [[ "$DEPS_OC_PACK" != *"hermes"* ]]; then
echo "PASS: non-troika pack unaffected by PRIMARY=hermes"
else
echo "FAIL: non-troika pack should not be affected; got: $DEPS_OC_PACK"
exit 1
fi

# Test 4: order preservation — bedrockify,<primary>,claude-code,codex-cli
PRIMARY="hermes"
deps_arr=()
while IFS= read -r dep; do
deps_arr+=("$dep")
done < <(get_effective_deps troika)
if [[ "${deps_arr[0]}" == "bedrockify" && "${deps_arr[1]}" == "hermes" \
&& "${deps_arr[2]}" == "claude-code" && "${deps_arr[3]}" == "codex-cli" ]]; then
echo "PASS: dep order preserved (bedrockify→hermes→claude-code→codex-cli)"
else
echo "FAIL: dep order wrong; got: ${deps_arr[*]}"
exit 1
fi

echo "ALL_PASS"
DEPTEST_EOF

DEP_OUT=$(bash "$_DEP_TEST_SCRIPT" "$REGISTRY" 2>&1) && DEP_RC=0 || DEP_RC=$?
rm -f "$_DEP_TEST_SCRIPT"

if echo "$DEP_OUT" | grep -q "FAIL"; then
fail "dep-substitution: at least one case failed — see above"
echo "$DEP_OUT" | grep -v PASS
elif echo "$DEP_OUT" | grep -q "ALL_PASS"; then
ok "dep-substitution: primary=openclaw no-op, primary=hermes substitutes, non-troika unaffected, order preserved"
else
fail "dep-substitution: unexpected output — got: $DEP_OUT"
fi
fi
echo ""

# ── Summary ───────────────────────────────────────────────────────────────────
echo "================================================================"
echo " Results: $PASS passed, $FAIL failed"
Expand Down
Loading