Skip to content
All projects

PF-06 · AI · FastAPI

AetherCrew: a virtual company of 11 AI employees

A hand-built orchestrator that assigns work, picks models, and tracks cost for 11 AI employees in a virtual company. Agent work is simulated and no language model API is called.

Role
Design and implementation
Period
Personal project · released Nov 2026
Cloud
None
Scale
11 AI employees · 3 divisions · 8 dashboard pages
Key tech
Python · FastAPI · WebSocket · pytest · Slack · Jira · GitHub
GitHub
11
AI employees
3
Divisions
8
Dashboard pages

At a glance

CEOUser directiveManagementTechnologySalesEmmaChief of staffMiaFinanceAvaHRSophiaTech leadNoahSenior engineerLunaEngineerEthanDB engineerLiamQA engineerChloeSecurity engineerJamesPlanning and salesleadOliviaContent marketer(off by default)Model priority · estimated cost recordedSimulated: assignment, model selection, and cost tracking onlyNo language model API is called
In the public code, agent work is simulated and no language model API is called.

Problem

When AI employees split work by role, code must control which model each one uses, how much has been spent, and what can be written to outside systems.

Approach

The chief of staff takes a CEO directive, writes a plan, and splits it into tasks by role. Each employee has a priority list of Anthropic, OpenAI, Google, and DeepSeek models and moves to the next on failure. Estimated costs add up, with warnings at 80% and 95% and work stopped at 100%. External writes are off by default, and file access stays inside the project root.

Result

Eight FastAPI dashboard pages show the org chart, cost, logs, and a pixel-art CCTV office. It runs with no API keys and calls no language model, so it produces no real work output. The Slack, Jira, and GitHub clients are real code and run only when INTEGRATIONS_LIVE_WRITE is on.

Up close

Model fallback per employeeThe first available model on the list is chosengpt-5OpenAIclaude-opus-4Anthropicgemini-2.5-proGoogledeepseek-v3DeepSeekChoice 1Choice 2Choice 3Choice 4Selectedagent.fallback event loggedEstimated cost only, no language model API call
The switch is logged as an agent.fallback event. Model calls are simulated.
Budget policy80% warning95% warning100% work stopsWork stopped
At 100%, work stops. Costs can be read by day, week, month, and employee.

Model fallback

What this code does: Picks the first available model from an employee priority list.

class FallbackManager:
    """Selects next available agent with bounded fallback traversal."""

    def select(
        self,
        priority_list: list[str],
        unavailable_agents: set[str] | None = None,
    ) -> FallbackResult:
        if not priority_list:
            raise ValueError("priority_list must not be empty")
        blocked = unavailable_agents or set()
        fallbacks: list[str] = []
        for idx, model in enumerate(priority_list):
            if model in blocked:
                fallbacks.append(model)
                continue
            return FallbackResult(selected_agent=model, selected_index=idx, fallbacks=fallbacks)
        raise RuntimeError("All agents are unavailable; fallback exhausted")

Budget states

What this code does: Turns the running cost into a budget state. At 100% or more, work stops.

    @property
    def total_cost(self) -> float:
        return round(sum(e.cost_usd for e in self.entries), 6)

    def percent_used(self) -> float:
        if self.budget_policy.total_budget <= 0:
            return 100.0
        return (self.total_cost / self.budget_policy.total_budget) * 100

    def budget_state(self) -> str:
        pct = self.percent_used()
        if pct >= self.budget_policy.hard_stop_at_pct:
            return "HARD_STOP"
        if pct >= self.budget_policy.warn_at_pct[1]:
            return "WARN_95"
        if pct >= self.budget_policy.warn_at_pct[0]:
            return "WARN_80"
        return "OK"

What I learned

When I let agents hand work to each other, a wrong result from one agent was trusted as-is by the next, with no check in between. That is when trust boundaries and permission scope became concrete to me.

What I would improve

Language model calls are not implemented. State is stored in one JSON file, which suits a single local process only.

Related field notes

Open to work · Cloud infrastructure and security