Skip to content

Quickstart

This guide builds a small incident-triage system using the Team-first public API. Team is the assembly door; runtime agents are created through team.add_agent(...) and then routed through the same Team policy and scope boundary.

1. Install

pip install "coactra[agent]"

For source development:

python -m pip install -e "./coactra[all,dev]"

2. Write A Tool

def get_runbook(service: str) -> str:
    runbooks = {
        "nginx": "https://wiki.example.com/runbooks/nginx",
        "postgres": "https://wiki.example.com/runbooks/postgres",
    }
    return runbooks.get(service, "https://wiki.example.com/runbooks/generic")

3. Create A Team And Add An Agent

import asyncio
import os

from coactra import ModelProfile, ModelResolver, ModelRoute, Policy, Scope, Skill, Team


def get_runbook(service: str) -> str:
    return f"https://wiki.example.com/runbooks/{service}"


async def main() -> None:
    team = Team(
        scope=Scope(tenant_id="acme", namespace="support"),
        policy=Policy.permissive(),
        model_resolver=ModelResolver([
            ModelRoute(
                capability="triage",
                profile=ModelProfile(
                    name="triage",
                    model="openai/deepseek-v4-pro",
                    api_base="https://opencode.ai/zen/go/v1",
                    api_key=os.environ["OC_KEY"],
                ),
            )
        ]),
    )
    agent = await team.add_agent(
        model_capability="triage",
        name="triage-agent",
        auth="dev-token",
        tools=[get_runbook],
        skills=[Skill(id="incident", description="Triage production incidents", tags=["triage"])],
        instructions="You are a senior SRE. Be concise and actionable.",
        expose=True,
    )
    answer = await agent.run("Triage nginx 502s on checkout")
    print(answer)


asyncio.run(main())

For deterministic local tests, route a capability to TestModel() or FunctionModel(...) instead of a live provider.

4. Add Memory, Workspace, Or Gateway Tools

import os

from coactra import ModelProfile, ModelResolver, ModelRoute, Policy, Scope, StaticToken, Team

team = Team(
    scope=Scope(tenant_id="acme", namespace="ops"),
    policy=Policy.permissive(),
    model_resolver=ModelResolver([
        ModelRoute(
            capability="sre",
            profile=ModelProfile(
                name="sre",
                model="openai/deepseek-v4-pro",
                api_base="https://opencode.ai/zen/go/v1",
                api_key=os.environ["OC_KEY"],
            ),
        )
    ]),
)
agent = await team.add_agent(
    model_capability="sre",
    name="sre-agent",
    gateway="https://gateway.example/mcp",
    auth=StaticToken("your-gateway-token"),
    memory="graphiti",
    workspace="./desk",
)

5. Route Across A Team

import os

from coactra import ModelProfile, ModelResolver, ModelRoute, Policy, Scope, Skill, Team

team = Team(
    scope=Scope(tenant_id="acme", namespace="security"),
    policy=Policy.permissive(),
    model_resolver=ModelResolver([
        ModelRoute(
            capability="security-review",
            profile=ModelProfile(
                name="security-review",
                model="openai/deepseek-v4-pro",
                api_base="https://opencode.ai/zen/go/v1",
                api_key=os.environ["OC_KEY"],
            ),
        )
    ]),
)
await team.add_agent(
    model_capability="security-review",
    name="security-reviewer",
    auth="dev-token",
    skills=[Skill(id="security", description="Review security-sensitive changes", tags=["review", "prod"])],
    expose=True,
)
await team.add_agent(
    model_capability="security-review",
    name="security-operator",
    auth="dev-token",
    skills=[Skill(id="security", description="Execute security changes", tags=["execute", "prod"])],
    expose=True,
)
print(team.match_skill("security", required_tags=["review"]).card)

Broad skill ids keep the roster portable. required_tags is the supported way to disambiguate overlapping specialists. team.match_skill("security") fails closed if the match is ambiguous.

6. Run A Workflow

from coactra import Workflow
from coactra.workflow import ProofBundle, VerificationReceipt, step

workflow = Workflow(
    "cert rotation",
    steps=[
        step("plan the change", requires_skill="security", required_tags=["review"]),
        step("apply the rotation", requires_skill="security", required_tags=["execute"]),
        step("human sign-off", approve=True, approval_only=True),
    ],
)

run = await team.run(workflow)
if run.status == "interrupted":
    run = await workflow.resume(
        run,
        team,
        decision={
            "approved": True,
            "proof_bundle": ProofBundle(
                summary="review completed and operator approved",
                receipts=[
                    VerificationReceipt(
                        command="openssl s_client -connect example.com:443",
                        exit_code=0,
                        stdout_sha256="abc123",
                    )
                ],
            ),
        },
    )

Workflow supports Team skill routing, approval pause/resume with proof bundles, checkpoint storage, and swappable engine seams. For the common implement/verify/review pattern, see coactra.agent.recipes.code_change(...) in Code Change Workflow.

7. Production Shape

Concern Development Production
Policy Policy.permissive() guarded / approval-gated custom policy
Scope Scope(tenant_id="acme", namespace="support") tenant/workspace-qualified scope
Model TestModel() / FunctionModel(...) route Team-owned routed profiles via ModelResolver
Tools local callables gateway= plus scoped auth
Memory in-process or named local backend Graphiti/mem0 adapter with tenant scope
Workspace local gated directory host-controlled workspace backend
Peers local runtime agents RemotePeer(...) over A2A

Coactra should give you stable seams. Keep business behavior in plain functions and let Team and Workflow own coordination, policy, and durable state.