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
177 changes: 176 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ jobs:
command: sudo apt-get update && sudo apt-get install -y pandoc
- run:
name: Static checks
command: make lint typecheck test-static docs-check
command: make lint typecheck test-static test-deploy docs-check
- run:
name: Build downloadable client
command: make client-build
- store_artifacts:
path: build/veetbot-client.pyz
destination: veetbot-client.pyz

contract:
docker:
Expand Down Expand Up @@ -96,6 +102,150 @@ jobs:
name: Live provider tests
command: make test-live

package-release:
docker:
- image: cimg/base:stable
resource_class: small
steps:
- checkout
- run:
name: Package immutable source release
command: |
mkdir -p build/release
git archive --format=tar.gz \
--output=build/release/veetbot-server.tar.gz "$CIRCLE_SHA1"
release_id="$(date -u +%Y%m%d-%H%M%S)-${CIRCLE_SHA1:0:7}"
printf '%s\n' "$release_id" > build/release/release-id
cd build/release
sha256sum veetbot-server.tar.gz > veetbot-server.tar.gz.sha256
- persist_to_workspace:
root: build/release
paths:
- veetbot-server.tar.gz
- veetbot-server.tar.gz.sha256
- release-id

deploy-app:
docker:
- image: cimg/base:stable
resource_class: small
steps:
- attach_workspace:
at: /tmp/veetbot-release
- add_ssh_keys:
fingerprints:
- "SHA256:vt3iKfD3dv6dxtjS+Tre6B1EH6408yvMHFrMpp64sao"
- run:
name: Configure deployment SSH trust
command: |
: "${DEPLOY_HOST:?set DEPLOY_HOST in the veetbot-production context}"
: "${DEPLOY_USER:?set DEPLOY_USER in the veetbot-production context}"
: "${DEPLOY_KNOWN_HOSTS:?set DEPLOY_KNOWN_HOSTS in the veetbot-production context}"
install -d -m 0700 "$HOME/.ssh"
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$HOME/.ssh/known_hosts"
chmod 0600 "$HOME/.ssh/known_hosts"
release_id="$(cat /tmp/veetbot-release/release-id)"
[[ "$release_id" =~ ^[0-9]{8}-[0-9]{6}-[0-9a-f]{7,40}$ ]]
printf 'export VEETBOT_RELEASE_ID=%q\n' "$release_id" >> "$BASH_ENV"
- run:
name: Stage and release application
command: |
stage="/opt/veetbot/releases/$VEETBOT_RELEASE_ID"
set +e
ssh -p "${DEPLOY_PORT:-22}" "$DEPLOY_USER@$DEPLOY_HOST" \
"if grep -Fqx 'VEETBOT_RELEASE_ID=$VEETBOT_RELEASE_ID' \
/opt/veetbot/current/.release.env 2>/dev/null; then \
exit 42; \
fi; \
mkdir -p '$stage'"
stage_status=$?
set -e
if [[ "$stage_status" == 42 ]]; then
echo "$VEETBOT_RELEASE_ID is already active; verifying it without restaging."
elif [[ "$stage_status" != 0 ]]; then
exit "$stage_status"
else
scp -P "${DEPLOY_PORT:-22}" \
/tmp/veetbot-release/veetbot-server.tar.gz \
/tmp/veetbot-release/veetbot-server.tar.gz.sha256 \
"$DEPLOY_USER@$DEPLOY_HOST:$stage/"
ssh -p "${DEPLOY_PORT:-22}" "$DEPLOY_USER@$DEPLOY_HOST" \
"cd '$stage' || exit 1; \
if ! sha256sum --check veetbot-server.tar.gz.sha256 || \
! tar -xzf veetbot-server.tar.gz; then \
cd / && rm -rf -- '$stage'; exit 1; \
fi; \
rm -f veetbot-server.tar.gz veetbot-server.tar.gz.sha256; \
bash deploy/app/release.sh '$VEETBOT_RELEASE_ID'"
fi
- run:
name: Verify public release identity
command: |
production_url="${PRODUCTION_URL:-https://api.veetbot.com}"
headers="$(mktemp)"
trap 'rm -f "$headers"' EXIT
attempt=0
max_attempts=10
while (( attempt < max_attempts )); do
attempt=$((attempt + 1))
: >"$headers"
if curl --fail --silent --show-error \
--connect-timeout 5 --max-time 15 \
--dump-header "$headers" --output /dev/null \
"$production_url/health/ready" \
&& awk -F ': *' -v expected="$VEETBOT_RELEASE_ID" '
BEGIN { IGNORECASE = 1 }
tolower($1) == "x-veetbot-release" {
sub(/\r$/, "", $2)
if ($2 == expected) found = 1
}
END { exit found ? 0 : 1 }
' "$headers"; then
exit 0
fi
if (( attempt < max_attempts )); then
sleep 5
fi
done
echo "production did not report release $VEETBOT_RELEASE_ID" >&2
exit 1

deploy-nginx:
docker:
- image: cimg/base:stable
resource_class: small
steps:
- checkout
- attach_workspace:
at: /tmp/veetbot-release
- add_ssh_keys:
fingerprints:
- "SHA256:vt3iKfD3dv6dxtjS+Tre6B1EH6408yvMHFrMpp64sao"
- run:
name: Configure deployment SSH trust
command: |
: "${DEPLOY_HOST:?set DEPLOY_HOST in the veetbot-production context}"
: "${DEPLOY_USER:?set DEPLOY_USER in the veetbot-production context}"
: "${DEPLOY_KNOWN_HOSTS:?set DEPLOY_KNOWN_HOSTS in the veetbot-production context}"
install -d -m 0700 "$HOME/.ssh"
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$HOME/.ssh/known_hosts"
chmod 0600 "$HOME/.ssh/known_hosts"
- run:
name: Validate and reload Nginx
command: |
expected_release_id="$(cat /tmp/veetbot-release/release-id)"
[[ "$expected_release_id" =~ ^[0-9]{8}-[0-9]{6}-[0-9a-f]{7,40}$ ]]
remote_config="/tmp/veetbot-nginx-$CIRCLE_SHA1.conf"
remote_script="/tmp/veetbot-nginx-$CIRCLE_SHA1.sh"
scp -P "${DEPLOY_PORT:-22}" nginx/veetbot.conf \
"$DEPLOY_USER@$DEPLOY_HOST:$remote_config"
scp -P "${DEPLOY_PORT:-22}" deploy/nginx/deploy.sh \
"$DEPLOY_USER@$DEPLOY_HOST:$remote_script"
ssh -p "${DEPLOY_PORT:-22}" "$DEPLOY_USER@$DEPLOY_HOST" \
"VEETBOT_EXPECTED_RELEASE_ID='$expected_release_id' \
bash '$remote_script' '$remote_config'; \
status=\$?; rm -f '$remote_script' '$remote_config'; exit \$status"

workflows:
verify:
unless: << pipeline.parameters.run_live >>
Expand All @@ -104,6 +254,31 @@ workflows:
- contract
- integration
- sandbox
- package-release:
requires:
- static
- contract
- integration
- sandbox
filters:
branches:
only: main
- deploy-app:
context: veetbot-production
serial-group: << pipeline.project.slug >>/veetbot-production
requires:
- package-release
filters:
branches:
only: main
- deploy-nginx:
context: veetbot-production
serial-group: << pipeline.project.slug >>/veetbot-production
requires:
- deploy-app
filters:
branches:
only: main

live_manual:
when: << pipeline.parameters.run_live >>
Expand Down
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Required in every deployment.
DATABASE_URL=postgresql+asyncpg://agent:agent@localhost:5432/agent
DEPLOYMENT_MODE=development
# Optional deployment identity written by the production release script.
VEETBOT_RELEASE_ID=

# Authentication. Token mode requires AUTH_TOKEN. Production refuses dev mode.
AUTH_MODE=dev
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated documentation outputs (canonical sources are Markdown/YAML).
/site/
/dist/
/build/

# Python
__pycache__/
Expand Down
15 changes: 11 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PYTHON ?= python

.PHONY: install format lint typecheck test check db-up migrate \
.PHONY: install format lint typecheck test check db-up migrate client-build \
test-static test-contract test-fast test-integration test-live \
test-sandbox sandbox-image \
test-sandbox test-deploy sandbox-image \
production-check \
docs docs-serve docs-check citations-fix

Expand All @@ -18,7 +18,10 @@ lint:
uv run ruff check .

typecheck:
uv run mypy src tests
uv run mypy src client tests scripts/build_client.py

client-build:
uv run $(PYTHON) scripts/build_client.py

test:
uv run pytest -m "not live"
Expand Down Expand Up @@ -46,6 +49,10 @@ test-live:
@RUN_LIVE_MODEL_TESTS=1 uv run pytest -m live; \
status=$$?; test $$status -eq 0 -o $$status -eq 5

test-deploy:
deploy/app/release.test.sh
deploy/nginx/deploy.test.sh

production-check:
uv run python scripts/check_production_deployment.py

Expand All @@ -61,7 +68,7 @@ docs-check:
citations-fix:
uv run $(PYTHON) scripts/check_citations.py --update

check: lint typecheck test-fast docs-check
check: lint typecheck test-fast test-deploy docs-check

db-up:
docker compose up -d postgres
Expand Down
51 changes: 40 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ make check
```

It runs formatting validation, linting, strict type checking, the static and
contract partitions, citation validation, the 172-entry gate-registry
contract partitions, isolated deployment-script tests, citation validation, the 172-entry gate-registry
reconciliation, and strict documentation builds. It requires neither a database
nor a provider credential.

Expand All @@ -83,7 +83,8 @@ Additional targets are explicit about their requirements:
| `make test-contract` | Run shared port contracts against in-memory/fake adapters |
| `make test-integration` | Run PostgreSQL, resilience, security, and eval-case tests |
| `make test-live` | Explicitly enable credentialed provider tests |
| `make production-check` | Validate production config, gVisor, sandbox image, storage, and migration head |
| `make test-deploy` | Exercise release and Nginx installers against isolated command stubs |
| `make production-check` | Validate release identity, model credential, gVisor, sandbox image, storage, and migration head |
| `make docs` | Build the MkDocs site and standalone HTML publication |
| `make docs-check` | Validate citations, registry structure, and strict docs output |

Expand Down Expand Up @@ -135,10 +136,38 @@ process supervisor to start that role.

Hosted checks use [CircleCI](https://circleci.com/) via
`.circleci/config.yml`. Connect the repository as a CircleCI project for the
static, contract, and integration workflow. Create a restricted context named
static, contract, integration, and sandbox workflow. A successful `main`
pipeline packages the tested commit and deploys it to `api.veetbot.com`; the
versioned proxy site is reconciled after the application release. Create a
restricted context named
`live-model` for provider credentials; nightly runs and manually triggered
pipelines with `run_live: true` are the only workflows that use it.

## Downloadable API client

Build the dependency-free terminal client with:

```bash
make client-build
```

The resulting `build/veetbot-client.pyz` needs Python 3.12 or newer but does
not need the server package or its dependencies. It defaults to the loopback
API; the API and worker must be started separately as described in
[Run the durable agent](#run-the-durable-agent) and the [client guide](docs/client.md).
For a deployment, export the URL and let the client request the bearer token
through its interactive no-echo prompt:

```bash
export VEETBOT_API_URL=https://agent.example.com
python build/veetbot-client.pyz
```

The client creates or resumes sessions, submits idempotently, reconnects and
replays SSE, handles approvals and user questions, and reconciles transient
output against the durable final message. See the
[client guide](docs/client.md) for its commands and security boundary.

## Configuration

Environment values are limited to deployment identity, addresses, and secrets.
Expand Down Expand Up @@ -186,14 +215,14 @@ here so availability is not confused with implementation:
| Run optional live-provider tests | Milestone 3 (implemented) |
| Inspect normalized model usage and bounded provider metadata in persistence | Milestone 3 (implemented) |
| Export a consent-gated redacted trajectory | Milestone 3 (implemented) |
| Resolve an approval through the CLI | Milestone 4 |
| Start the HTTP API | Milestone 5 |
| Resolve an approval through the CLI | Milestone 4 (implemented) |
| Start the HTTP API | Milestone 5 (implemented) |
| Run deterministic evaluation cases | Milestone 1; later cases activate with their owning milestone |

`agent run`, `agent run export`, `agent session create`, `agent session
export-consent`, `agent worker`, and `agent eval run` are available now. Do not
invoke `agent api` or `agent chat`; their owning milestones have not been
implemented.
export-consent`, `agent worker`, `agent api`, and `agent eval run` are available
now. The separately downloadable client provides interactive remote chat;
`agent chat` itself remains unavailable.

## Documentation and governance

Expand All @@ -212,6 +241,6 @@ Security boundaries and the controls established so far are documented in
[docs/security.md](docs/security.md).

Production operators should follow the evidence-based
[DigitalOcean deployment runbook](docs/deployment.md). It includes the checked-in
systemd, Caddy, production environment, gVisor, and preflight assets and leaves
server-specific steps unchecked until they are verified on the target host.
[DigitalOcean deployment runbook](docs/deployment.md). It covers the checked-in
atomic release script, systemd, Nginx, production environment, gVisor,
CircleCI context, rollback procedure, and remaining host bootstrap work.
1 change: 1 addition & 0 deletions client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Source package for independently distributed clients."""
3 changes: 3 additions & 0 deletions client/veetbot_client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Thin, dependency-free client for the Veetbot HTTP API."""

__version__ = "0.1.0.dev0"
Loading