Skip to content

Your first policy

This builds a policy for an agent that helps a crew keep their tasks up to date, one step at a time. The app has a Tasks table with Title, Status, Notes, Due Date, Project, Amount, which holds what a task costs, and Code, an internal reference.

Each step asks the bound policy what it allows, with allows() and explain(), which send nothing.

1. Start read-only

Policy.read_only() reads whatever the token can and changes nothing. It names no table, so it needs no binding. Start here, and loosen it only where the job needs to.

from pyquickbase import Policy, Quickbase

with Quickbase.from_env(policy=Policy.read_only()) as qb:
    tasks = qb.app("bq1234567").table("Tasks")
    for task in tasks.query(select=["Title"], max_total=3):
        print(task["Title"])

2. Allow what the job needs

The agent reads tasks and changes their status and notes. Anything no rule allows is refused.

from pyquickbase import Policy, Quickbase, allow

policy = Policy(
    rules=[
        allow.read("Tasks", max_paging_depth=50),
        allow.update("Tasks", ["Status", "Notes"]),
    ]
)
with Quickbase.from_env() as setup:
    bound = policy.bind(setup.app("bq1234567"))

print(bound.allows("update", "Tasks", field="Status").allowed)  # True
print(bound.allows("update", "Tasks", field="Due Date").allowed)  # False

max_paging_depth=50 means one query can reach at most 50 records, so a mistake can't page through the whole table.

3. Hide what it shouldn't see

Costs and internal codes aren't the agent's business. Put them in a class and deny the class. A deny always wins over an allow, and a formula, lookup, or summary that reads a denied field is denied too.

from pyquickbase import Policy, Quickbase, allow, deny, fields

policy = Policy(
    classes={"internal": ["Tasks.Amount", "Tasks.Code"]},
    rules=[
        allow.read("Tasks", max_paging_depth=50),
        allow.update("Tasks", ["Status", "Notes"]),
        deny.all(fields.classed("internal")),
    ],
)
with Quickbase.from_env() as setup:
    bound = policy.bind(setup.app("bq1234567"))

print(bound.allows("read", "Tasks", field="Amount").allowed)  # False
print(bound.explain("read", "Tasks", field="Amount"))

To a client with this policy, Amount and Code look as if they don't exist.

4. Keep it to some records

limit.rows() ANDs a condition onto every query, summary, and delete, so the agent only reaches one project's tasks.

from pyquickbase import F, Policy, Quickbase, allow, deny, fields, limit

policy = Policy(
    classes={"internal": ["Tasks.Amount", "Tasks.Code"]},
    rules=[
        allow.read("Tasks", max_paging_depth=50),
        allow.update("Tasks", ["Status", "Notes"]),
        deny.all(fields.classed("internal")),
        limit.rows("Tasks", F("Project") == "Roof"),
    ],
)
with Quickbase.from_env() as setup:
    bound = policy.bind(setup.app("bq1234567"))
print(bound.row_limit(setup.app("bq1234567").table("Tasks").info().id))

The agent now sees the Tasks table like this:

flowchart TD
    subgraph T["Tasks where Project is Roof"]
        U["<b>Read and update</b><br/>Status, Notes"]
        R["<b>Read only</b><br/>Title, Due Date, Project"]
        H["<b>Hidden</b><br/>Amount, Code"]
    end
    classDef hidden fill:#e5393526,stroke:#e53935
    class H hidden

5. Use it

Give the bound policy to the agent's client. A refused request raises PolicyRefusedError before anything is sent, and its message says what would allow it, so you can return it to the model as the tool's result.

from pyquickbase import Policy, PolicyRefusedError, Quickbase, allow

policy = Policy(rules=[allow.read("Tasks"), allow.update("Tasks", ["Status", "Notes"])])
with Quickbase.from_env() as setup:
    bound = policy.bind(setup.app("bq1234567"))

with Quickbase.from_env(policy=bound) as qb:
    tasks = qb.app("bq1234567").table("Tasks")
    tasks.update([{"Record ID#": 1, "Status": "Done"}])  # Allowed
    try:
        tasks.update([{"Record ID#": 1, "Due Date": None}])
    except PolicyRefusedError as error:
        print(error.decision.hint)

Next, see Writing rules for every rule, and Recipes for complete policies.