# AgentPaaS Platform Documentation

AgentPaaS is a Platform-as-a-Service for deploying, managing, and running Lambda-calculus-based AI agents. It wraps the lambdagent DSL with a multi-tenant API server, persistent sessions, provider management, and monitoring.

---

## Architecture Overview

```
                    CLI / HTTP Client
                          |
                    ┌─────▼─────┐
                    │  FastAPI   │   agentpaas/src/agentpaas/api/app.py
                    │  Server    │   (serve --dev)
                    └─────┬─────┘
                          |
           ┌──────────────┼──────────────┐
           |              |              |
    ┌──────▼──────┐ ┌────▼────┐ ┌───────▼───────┐
    │ Agent CRUD  │ │ Auth /  │ │ Status /      │
    │ + Execution │ │ Tenant  │ │ Metrics       │
    └──────┬──────┘ └─────────┘ └───────────────┘
           |
    ┌──────▼──────────────────────┐
    │   lambdagent Engine         │
    │   (Compiler + Runtime)      │
    │                             │
    │   YAML → Lambda Term → CEK  │
    │   ConversationLam (session) │
    └──────┬──────────────────────┘
           |
    ┌──────▼──────────────────────┐
    │   Unified Provider System   │
    │                             │
    │   claude-code  (no API key) │
    │   anthropic    (API key)    │
    │   ollama       (local)      │
    │   openai       (API key)    │
    │   dashscope    (API key)    │
    └─────────────────────────────┘
```

**Key components:**

- **FastAPI Server** (`agentpaas serve`) — REST API with multi-tenant auth, SSE streaming, OpenAPI docs at `/docs`.
- **Agent Store** — SQLite/Postgres-backed CRUD for agent configs, versions, run history.
- **lambdagent Engine** — Compiles YAML into Lambda terms, executes via CEK machine with ReAct loops, tool routing, and beta-reduction tracing.
- **ConversationLam** — Session-persistent Lambda abstraction that maintains conversation history across turns.
- **Unified Provider System** — Five core providers with automatic detection and YAML-based switching.

---

## Agent Lifecycle

### 1. Create

Define an agent via YAML config and deploy to the platform:

```bash
# Write YAML config
cat > my-agent.yml <<'EOF'
agentId: my-agent
name: MyAgent
type: react
model:
  provider: claude-code
  name: sonnet
systemPrompt: "You are a helpful research assistant."
react:
  maxSteps: 10
EOF

# Lint before deploying
python -m lambdagent lint my-agent.yml

# Deploy to PaaS
python -m agentpaas agent create --name my-agent --config my-agent.yml
# Output: Agent created: ag_abc123
```

### 2. Deploy

The agent is live immediately after `agent create`. The PaaS server compiles the YAML into a Lambda term at execution time, so no build step is needed. The API endpoint is:

```
POST /api/v1/agents/{agent_id}/run
POST /api/v1/agents/{agent_id}/run/stream   (SSE)
```

### 3. Chat (Interactive)

Use `agentpaas chat` for interactive multi-turn conversation:

```bash
agentpaas chat my-agent
```

The chat command resolves the agent by name (fuzzy match) or ID, opens an interactive session with SSE streaming, and maintains conversation context server-side.

With `provider: claude-code` in the YAML config, no API key is required -- the agent calls Claude Code CLI directly.

### 4. Update

Iterate on the agent config with automatic versioning:

```bash
# Edit config, then update
python -m agentpaas agent update ag_abc123 --config my-agent-v2.yml --changelog "Added search tools"

# View version history
python -m agentpaas agent versions ag_abc123

# Rollback if needed
python -m agentpaas agent rollback ag_abc123 --version 1
```

Each update increments the version. If the config content is unchanged, no new version is created.

---

## Provider Configuration

AgentPaaS uses a unified provider system. The provider is specified in the agent's YAML config under `model.provider`:

| Provider | Type | API Key Required | Notes |
|----------|------|------------------|-------|
| `claude-code` | Claude Code CLI | No | Uses Claude Code Max Plan subscription |
| `anthropic` | Anthropic API | Yes (`ANTHROPIC_API_KEY`) | Direct API access |
| `ollama` | Local inference | No | Requires `ollama serve` running locally |
| `openai` | OpenAI API | Yes (`OPENAI_API_KEY`) | GPT models |
| `dashscope` | Alibaba Cloud | Yes (`DASHSCOPE_API_KEY`) | Qwen models |

### Provider in YAML

```yaml
model:
  provider: claude-code   # Switch provider here
  name: sonnet            # Model name (provider-specific)
  temperature: 0.0
```

### Provider detection priority

When `provider` is not specified, the system auto-detects:

1. If model name has a known prefix (e.g., `dashscope/qwen-max`), use that provider.
2. If model name matches known patterns (e.g., `claude-*` -> anthropic, `gpt-*` -> openai).
3. Fall back to environment variable detection.

### Managing providers via CLI

```bash
# List providers and status
python -m agentpaas provider list

# Test a provider connection
python -m agentpaas provider test anthropic

# Add/configure a provider
python -m agentpaas provider add dashscope --api-key sk-xxxx
```

---

## Session Persistence via ConversationLam

`ConversationLam` is the core mechanism for multi-turn conversation support. It wraps any `LLMProvider` with conversation history management.

### Lambda semantics

```
ConversationLam(provider, prompt) = lambda x. provider(history ++ [x])
```

Each `apply()` call:
1. Appends the input as a user message to the history.
2. Calls the provider with the full conversation (or windowed subset).
3. Records the assistant response.
4. Returns the response.

### Key difference from stateless Lam

| | `Lam` | `ConversationLam` |
|---|---|---|
| State | Stateless -- each call is independent | Stateful -- builds on all previous calls |
| History | None | Full message list with windowing |
| Use case | Single-shot tasks | Multi-turn chat, ReAct loops |

### Configuration

```python
from lambdagent.conversation import ConversationLam
from lambdagent.providers.claude_code_provider import ClaudeCodeProvider

provider = ClaudeCodeProvider(config)
lam = ConversationLam(
    name="agent",
    provider=provider,
    system_prompt="You are a helpful assistant.",
    max_history_tokens=80000,   # Token window limit
    keep_recent_turns=20,       # Minimum recent turns to keep
)

r1 = lam.apply("read the README")       # Creates conversation
r2 = lam.apply("[tool result] ...")       # Continues with full memory
r3 = lam.apply("summarize what we did")   # Still remembers r1, r2
```

This is what eliminates hallucination in multi-step ReAct agents: the LLM sees its complete conversation history, not a lossy compressed state string.

---

## MCP Isolation: --strict-mcp-config

By default, agents inherit globally available MCP tools. The `--strict-mcp-config` flag (or `strictMcpConfig: true` in YAML) enables MCP isolation: the agent can only access tools explicitly declared in its own config.

```yaml
strictMcpConfig: true
mcp:
  onlineTool:
    my-server:
      - search
      - read_file
  localTools:
    - terminate
```

This is important for multi-tenant deployments where agents should not access each other's tools.

---

## API Endpoints Overview

All endpoints are under `/api/v1/` and require an API key header (`X-API-Key`).

### Agents

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/agents` | Create agent |
| `GET` | `/agents` | List agents |
| `GET` | `/agents/{id}` | Get agent details |
| `PUT` | `/agents/{id}` | Update agent config |
| `DELETE` | `/agents/{id}` | Delete agent (soft) |
| `POST` | `/agents/{id}/rollback` | Rollback to version |
| `GET` | `/agents/{id}/versions` | List versions |

### Execution

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/agents/{id}/run` | Execute agent (sync) |
| `POST` | `/agents/{id}/run/stream` | Execute agent (SSE streaming) |
| `GET` | `/agents/{id}/runs` | List run history |
| `POST` | `/agents/{id}/runs/record` | Record external run |

### Auth & Tenants

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/auth/keys` | Create API key |
| `GET` | `/auth/keys` | List API keys |
| `DELETE` | `/auth/keys/{id}` | Revoke API key |
| `POST` | `/admin/tenants` | Create tenant |
| `GET` | `/admin/tenants/{id}` | Get tenant |
| `PUT` | `/admin/tenants/{id}/quota` | Update quota |

### Monitoring

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/status` | Platform overview |
| `GET` | `/status/agents` | All agents status |
| `GET` | `/status/agents/{id}` | Single agent status |
| `GET` | `/status/agents/{id}/health` | Health score |
| `GET` | `/metrics/overview` | Platform metrics |
| `GET` | `/billing/usage` | Usage/billing data |

### Traces

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/traces/{run_id}` | Get run trace |
| `GET` | `/traces/{run_id}/tools` | Get tool calls |
| `POST` | `/traces/{run_id}/confirm` | Confirm tool action |

### Other

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/health` | Server health check |
| `GET` | `/.well-known/agent.json` | Agent discovery |
| `GET` | `/jobs/{id}` | Async job status |
| `POST` | `/jobs/{id}/cancel` | Cancel async job |
