# In code

> Write an agent against the engine, in Python.

An agent in code is a **card** in your pack’s catalogue and, for a `python` card, an async function. The engine’s orchestrator runs the cards in dependency order, each under its own card, and holds any output with an external effect for a person’s sign-off.

The engine is Atlantic’s Python package `aal_engine`, delivered to your deployment as a versioned package. A public package and SDK are \[Coming].

## The agent card in code

A catalogue is a YAML file in your own repository. This one has two agents: `summary` counts notes and writes one line (an internal effect: it runs unattended); `publish` takes that line and would publish it (an external effect at autonomy 1: it waits for a person).

```yaml
pack: research
agents:
  - id: summary
    purpose: Count the notes in the inbox folder and write a one-line summary.
    executor: python
    target: research_agents:summary
    overseer: ops@example.com
    budget_usd: 1.00
    effect: internal
    produces: out/summary-{run}.txt
  - id: publish
    purpose: Publish the summary to the team page.
    executor: python
    target: research_agents:publish
    overseer: ops@example.com
    budget_usd: 0.50
    needs: [summary]
    effect: external
    autonomy: 1
    produces: out/published-{run}.txt
```

`id`, `purpose`, `executor`, `overseer` and `budget_usd` are required. `produces` is the artefact that proves the agent ran: an agent that exits cleanly having written nothing has failed. For every field, see [Agents](/concepts/agents/#the-agent-card).

## The agent’s code

A `python` card’s `target` is `module:function`, an async function that takes the agent’s context and returns an `Outcome`:

```python
from pathlib import Path

from aal_engine.orchestrator import AgentContext, Outcome


async def summary(ctx: AgentContext) -> Outcome:
    notes = sorted(Path("inbox").glob("*.md"))
    out = ctx.artefact()
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(f"{len(notes)} notes in the inbox\n")
    return Outcome(note=f"{len(notes)} notes")


async def publish(ctx: AgentContext) -> Outcome:
    out = ctx.artefact()
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(Path(ctx.inputs["summary"]).read_text())
    return Outcome(note="ready to publish")
```

The context gives the agent its card, its run ID, its working directory, the artefacts of the cards it `needs` (`ctx.inputs`), and the model through the gateway (`await ctx.model(prompt)`), within the card’s budget.

## Tools and handlers

- **Tools**: a card’s `tools` is the whole list; the executor grants nothing else, and a pack’s `deny_tools` are refused to every agent. With run tokens on, each tool call is checked against a Cedar policy generated from the card. See [The gateway](/concepts/gateway/#the-tool-gate).
- **Handlers**: whoever decides on the `publish` output is a handler. In a deployment whose approvals are managed by LightHouse, the decision is made there and reaches the engine as a signed command. See [Handlers](/concepts/handlers/).

## Local runs

```python
import json
from pathlib import Path

from aal_engine.catalog import load
from aal_engine.orchestrator import Orchestrator

orch = Orchestrator(load("catalog.yaml"), client="example",
                    state_dir=Path("var/state"), workdir=Path("."))
state = orch.run("run-0001")
print(json.dumps(orch.summary(state), indent=2))
```

With two notes in `inbox/`, `PYTHONPATH=. python run.py` prints:

```json
{
  "by_status": {
    "ok": [
      "summary"
    ],
    "awaiting-approval": [
      "publish"
    ]
  },
  "failed_hard": [],
  "cost_usd": 0.0,
  "ok": true
}
```

`publish` is waiting, not failed. Running `run-0001` again resumes it: an agent already done is not run again. Locally, with no LightHouse policy in force, a named person can decide in code:

```python
orch.decide("run-0001", "publish", approve=True, by="ops@example.com")
```

In a deployment whose approvals are managed by LightHouse, that same call is refused (`approvals-managed-by-insights`): the decision is made in LightHouse.

A source of knowledge is a card too, with the target `aal_engine.ingest.connector:run_card` and a `source` block. See [Connect your estate](/connect/upload/#connect-in-code).

## Releasing

1. Merge the catalogue and code change into your deployment’s repository, reviewed.
2. Run the evaluations and the release gate on the new version. Only a version that passes can be made current. See [Evaluations](/build/evaluations/).
3. Make it current: a handler’s `release.add` command from LightHouse, or the release ledger in the deployment. See [Versioning and rollback](/build/versioning/).
4. Watch it in **Agents → Activity** and the [run map](/build/run-map/).

Deploying from a command line (`lighthouse agents deploy`) is \[Coming].

Source: https://docs.atlanticagenticlabs.com/build/code/
